Migrate project from typing_practiceweb
7
html5-tower-defense-master/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.idea
|
||||
*.iml
|
||||
*.pyc
|
||||
build/td-pkg.js
|
||||
src/css/c-min.css
|
||||
node_modules
|
||||
npm-debug.log
|
||||
21
html5-tower-defense-master/License.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 oldj
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
65
html5-tower-defense-master/README.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# HTML5 塔防游戏
|
||||
|
||||
|
||||
* Author: oldj
|
||||
* Email: oldj.wu@gmail.com
|
||||
* Blog: [http://oldj.net/](http://oldj.net/)
|
||||
* Source: https://github.com/oldj/html5-tower-defense
|
||||
* License: MIT
|
||||
|
||||
|
||||
## 运行
|
||||
|
||||
进入 `src` 或 `build` 目录,用浏览器(如 Chrome、IE9 )打开 td.html 即可运行本游戏。
|
||||
|
||||
或者查看[线上Demo](http://oldj.net/static/html5-tower-defense/td.html)。
|
||||
|
||||
## 说明
|
||||
|
||||
1. 本游戏完全使用 HTML5 / JavaScript / CSS 实现,没有用到 Flash、SilverLight 等技术。
|
||||
2. 这一个版本没有用到图片,游戏中的所有物品都是使用 HTML5 画出来的。
|
||||
3. 这一个版本部分地方为 IE9 做了专门的优化,可正常运行在 IE9 下。
|
||||
|
||||
|
||||
## 目录
|
||||
|
||||
/build 压缩后的可发布的文件
|
||||
/screenshorts 屏幕截图
|
||||
/src 源码
|
||||
/css 样式表
|
||||
/js JavaScripts 源文件
|
||||
/tools 小工具、脚本
|
||||
/README.md 本文件
|
||||
|
||||
|
||||
## 作弊方法
|
||||
|
||||
为方便测试,本游戏内置了几个作弊方法,在命令行中执行如下命令即可:
|
||||
|
||||
1. 增加 100 万金钱:`_TD.cheat="money+";`
|
||||
2. 难度增倍:`_TD.cheat="difficulty+";`
|
||||
3. 难度减半:`_TD.cheat="difficulty-";`
|
||||
4. 生命值恢复:`_TD.cheat="life+";`
|
||||
5. 生命值降为最低:`_TD.cheat="life-";`
|
||||
|
||||
注意,以上作弊方法主要是为测试设计,正常游戏过程中请酌情使用,否则可能会降低游戏乐趣。
|
||||
|
||||
|
||||
## 更新历史
|
||||
|
||||
- 2015-09-06 支持 retina 显示屏。
|
||||
- 2011-01-01 调整参数,同时根据网友建议,新建建筑时添加检查,禁止用建筑把怪物包围起来(v0.1.14)。
|
||||
- 2010-12-29 根据网友建议,增加生命自动恢复功能(每隔 5 波生命恢复 5 点,每隔 10 波生命恢复 10 点)。调整参数,减小了激光枪的射程,增强了重机枪的威力(v0.1.12)。
|
||||
- 2010-12-18 添加新武器“激光枪”(v0.1.8.0)。
|
||||
- 2010-12-12 暂停图片资源版本分支的开发,继续优化、开发圈圈版(v0.1.7.0)。
|
||||
- 2010-11-28 第一个图片资源版本(v0.2.1.3267)。
|
||||
- 2010-11-23 发布 [圈圈版(v0.1.6.2970)](http://oldj.net/article/html5-td-circle-version/)。
|
||||
- 2010-11-14 线上发布第一个版本。
|
||||
- 2010-11-11 开始编写这个游戏。
|
||||
|
||||
|
||||
## 开发计划
|
||||
|
||||
- 添加新武器“加农炮”,特性:击中怪物时会发生爆炸,造成面攻击。
|
||||
- 添加关卡编辑器。
|
||||
- 添加保存进度的功能。
|
||||
61
html5-tower-defense-master/gulpfile.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* author: oldj
|
||||
*/
|
||||
|
||||
var gulp = require("gulp");
|
||||
var uglify = require("gulp-uglify");
|
||||
var browserify = require("gulp-browserify");
|
||||
var concat = require("gulp-concat");
|
||||
var sourcemaps = require("gulp-sourcemaps");
|
||||
var args = require("yargs").argv;
|
||||
|
||||
var IS_DEBUG = !!args.debug;
|
||||
console.log("IS_DEBUG: ", IS_DEBUG);
|
||||
console.log("--------------------");
|
||||
|
||||
gulp.task("scripts", function () {
|
||||
function doTask(lang) {
|
||||
gulp.src([
|
||||
"src/js/td.js"
|
||||
, "src/js/td-lang.js"
|
||||
, "src/js/td-event.js"
|
||||
, "src/js/td-stage.js"
|
||||
, "src/js/td-element.js"
|
||||
, "src/js/td-obj-map.js"
|
||||
, "src/js/td-obj-grid.js"
|
||||
, "src/js/td-obj-building.js"
|
||||
, "src/js/td-obj-monster.js"
|
||||
, "src/js/td-obj-panel.js"
|
||||
, "src/js/td-data-stage-1.js"
|
||||
, "src/js/td-cfg-buildings.js"
|
||||
, "src/js/td-cfg-monsters.js"
|
||||
, "src/js/td-render-buildings.js"
|
||||
, "src/js/td-msg-" + (lang || "") + ".js"
|
||||
, "src/js/td-walk.js"
|
||||
|
||||
])
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat("td-pkg-" + lang + "-min.js"))
|
||||
.pipe(uglify({
|
||||
compress: {
|
||||
drop_console: !IS_DEBUG
|
||||
}
|
||||
}))
|
||||
.pipe(sourcemaps.write("./"))
|
||||
.pipe(gulp.dest("build"))
|
||||
;
|
||||
}
|
||||
|
||||
doTask("zh");
|
||||
doTask("en");
|
||||
});
|
||||
|
||||
gulp.task("default", function () {
|
||||
gulp.start("scripts");
|
||||
gulp.watch([
|
||||
"src/**/*.js"
|
||||
], [
|
||||
"scripts"
|
||||
]);
|
||||
});
|
||||
|
||||
6004
html5-tower-defense-master/package-lock.json
generated
Normal file
27
html5-tower-defense-master/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "html5-tower-defense",
|
||||
"version": "1.0.0",
|
||||
"description": "HTML5 塔防游戏",
|
||||
"main": "td.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/oldj/html5-tower-defense.git"
|
||||
},
|
||||
"author": "oldj",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/oldj/html5-tower-defense/issues"
|
||||
},
|
||||
"homepage": "https://github.com/oldj/html5-tower-defense#readme",
|
||||
"devDependencies": {
|
||||
"gulp": "~3.9.0",
|
||||
"gulp-browserify": "~0.5.1",
|
||||
"gulp-concat": "~2.6.0",
|
||||
"gulp-sourcemaps": "^1.5.2",
|
||||
"gulp-uglify": "~1.4.0",
|
||||
"yargs": "^3.24.0"
|
||||
}
|
||||
}
|
||||
BIN
html5-tower-defense-master/screenshots/1.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
html5-tower-defense-master/screenshots/10.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
html5-tower-defense-master/screenshots/2.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
html5-tower-defense-master/screenshots/3.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
html5-tower-defense-master/screenshots/4.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
html5-tower-defense-master/screenshots/5.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
html5-tower-defense-master/screenshots/6.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
html5-tower-defense-master/screenshots/7.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
html5-tower-defense-master/screenshots/8.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
html5-tower-defense-master/screenshots/9.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
60
html5-tower-defense-master/src/css/c.css
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* c.css
|
||||
*
|
||||
* @save-up: [../js/td.js, ../td.html]
|
||||
*
|
||||
* last update: 2010-11-22 10:50:50
|
||||
*/
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
font-family: Verdana, "Times New Roman", serif;
|
||||
background: #1A74BA;
|
||||
}
|
||||
h1 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: 48px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
font-family: Verdana, "Times New Roman", serif;
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
margin: 0 auto;
|
||||
}
|
||||
#td-wrapper {
|
||||
padding: 8px 24px 60px 24px;
|
||||
background: #E0F4FC;
|
||||
/* constrain width to center the game inside the iframe */
|
||||
max-width: 920px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#td-board {
|
||||
display: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
#td-board canvas#td-canvas {
|
||||
position: relative;
|
||||
display: block; /* ensure margin auto centering works */
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
border: solid 1px #cdf;
|
||||
}
|
||||
#td-loading {
|
||||
font-size: 18px;
|
||||
line-height: 48px;
|
||||
padding: 60px 0 120px 0;
|
||||
font-style: italic;
|
||||
}
|
||||
#about {
|
||||
color: #fff;
|
||||
padding: 8px 24px;
|
||||
}
|
||||
#about a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
BIN
html5-tower-defense-master/src/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
99
html5-tower-defense-master/src/js/td-cfg-buildings.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* 本文件定义了建筑的参数、属性
|
||||
*/
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
/**
|
||||
* 默认的升级规则
|
||||
* @param old_level {Number}
|
||||
* @param old_value {Number}
|
||||
* @return new_value {Number}
|
||||
*/
|
||||
TD.default_upgrade_rule = function (old_level, old_value) {
|
||||
return old_value * 1.2;
|
||||
};
|
||||
|
||||
/**
|
||||
* 取得建筑的默认属性
|
||||
* @param building_type {String} 建筑类型
|
||||
*/
|
||||
TD.getDefaultBuildingAttributes = function (building_type) {
|
||||
|
||||
var building_attributes = {
|
||||
// 路障
|
||||
"wall": {
|
||||
damage: 0,
|
||||
range: 0,
|
||||
speed: 0,
|
||||
bullet_speed: 0,
|
||||
life: 100,
|
||||
shield: 500,
|
||||
cost: 5
|
||||
},
|
||||
|
||||
// 炮台
|
||||
"cannon": {
|
||||
damage: 12,
|
||||
range: 4,
|
||||
max_range: 8,
|
||||
speed: 2,
|
||||
bullet_speed: 6,
|
||||
life: 100,
|
||||
shield: 100,
|
||||
cost: 300,
|
||||
_upgrade_rule_damage: function (old_level, old_value) {
|
||||
return old_value * (old_level <= 10 ? 1.2 : 1.3);
|
||||
}
|
||||
},
|
||||
|
||||
// 轻机枪
|
||||
"LMG": {
|
||||
damage: 5,
|
||||
range: 5,
|
||||
max_range: 10,
|
||||
speed: 3,
|
||||
bullet_speed: 6,
|
||||
life: 100,
|
||||
shield: 50,
|
||||
cost: 100
|
||||
},
|
||||
|
||||
// 重机枪
|
||||
"HMG": {
|
||||
damage: 30,
|
||||
range: 3,
|
||||
max_range: 5,
|
||||
speed: 3,
|
||||
bullet_speed: 5,
|
||||
life: 100,
|
||||
shield: 200,
|
||||
cost: 800,
|
||||
_upgrade_rule_damage: function (old_level, old_value) {
|
||||
return old_value * 1.3;
|
||||
}
|
||||
},
|
||||
|
||||
// 激光枪
|
||||
"laser_gun": {
|
||||
damage: 25,
|
||||
range: 6,
|
||||
max_range: 10,
|
||||
speed: 20,
|
||||
// bullet_speed: 10, // laser_gun 的 bullet_speed 属性没有用
|
||||
life: 100,
|
||||
shield: 100,
|
||||
cost: 2000
|
||||
}
|
||||
};
|
||||
|
||||
return building_attributes[building_type] || {};
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
194
html5-tower-defense-master/src/js/td-cfg-monsters.js
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* 本文件定义了怪物默认属性及渲染方法
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
/**
|
||||
* 默认的怪物渲染方法
|
||||
*/
|
||||
function defaultMonsterRender() {
|
||||
if (!this.is_valid || !this.grid) return;
|
||||
var ctx = TD.ctx;
|
||||
|
||||
// 画一个圆代表怪物
|
||||
ctx.strokeStyle = "#000";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.cx, this.cy, this.r, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
// 画怪物的生命值
|
||||
if (TD.show_monster_life) {
|
||||
var s = Math.floor(TD.grid_size / 4),
|
||||
l = s * 2 - 2 * _TD.retina;
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.beginPath();
|
||||
ctx.fillRect(this.cx - s, this.cy - this.r - 6, s * 2, 4 * _TD.retina);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = "#f00";
|
||||
ctx.beginPath();
|
||||
ctx.fillRect(this.cx - s + _TD.retina, this.cy - this.r - (6 - _TD.retina), this.life * l / this.life0, 2 * _TD.retina);
|
||||
ctx.closePath();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得怪物的默认属性
|
||||
* @param [monster_idx] {Number} 怪物的类型
|
||||
* @return attributes {Object}
|
||||
*/
|
||||
TD.getDefaultMonsterAttributes = function (monster_idx) {
|
||||
|
||||
var monster_attributes = [
|
||||
{
|
||||
// idx: 0
|
||||
name: "monster 1",
|
||||
desc: "最弱小的怪物",
|
||||
speed: 3,
|
||||
max_speed: 10,
|
||||
life: 50,
|
||||
damage: 1, // 到达终点后会带来多少点伤害(1 ~ 10)
|
||||
shield: 0,
|
||||
money: 5 // 消灭本怪物后可得多少金钱(可选)
|
||||
},
|
||||
{
|
||||
// idx: 1
|
||||
name: "monster 2",
|
||||
desc: "稍强一些的小怪",
|
||||
speed: 6,
|
||||
max_speed: 20,
|
||||
life: 50,
|
||||
damage: 2, // 到达终点后会带来多少点伤害(1 ~ 10)
|
||||
shield: 1
|
||||
},
|
||||
{
|
||||
// idx: 2
|
||||
name: "monster speed",
|
||||
desc: "速度较快的小怪",
|
||||
speed: 12,
|
||||
max_speed: 30,
|
||||
life: 50,
|
||||
damage: 3, // 到达终点后会带来多少点伤害(1 ~ 10)
|
||||
shield: 1
|
||||
},
|
||||
{
|
||||
// idx: 3
|
||||
name: "monster life",
|
||||
desc: "生命值很强的小怪",
|
||||
speed: 5,
|
||||
max_speed: 10,
|
||||
life: 500,
|
||||
damage: 3, // 到达终点后会带来多少点伤害(1 ~ 10)
|
||||
shield: 1
|
||||
},
|
||||
{
|
||||
// idx: 4
|
||||
name: "monster shield",
|
||||
desc: "防御很强的小怪",
|
||||
speed: 5,
|
||||
max_speed: 10,
|
||||
life: 50,
|
||||
damage: 3, // 到达终点后会带来多少点伤害(1 ~ 10)
|
||||
shield: 20
|
||||
},
|
||||
{
|
||||
// idx: 5
|
||||
name: "monster damage",
|
||||
desc: "伤害值很大的小怪",
|
||||
speed: 7,
|
||||
max_speed: 14,
|
||||
life: 50,
|
||||
damage: 10, // 到达终点后会带来多少点伤害(1 ~ 10)
|
||||
shield: 2
|
||||
},
|
||||
{
|
||||
// idx: 6
|
||||
name: "monster speed-life",
|
||||
desc: "速度、生命都较高的怪物",
|
||||
speed: 15,
|
||||
max_speed: 30,
|
||||
life: 100,
|
||||
damage: 3, // 到达终点后会带来多少点伤害(1 ~ 10)
|
||||
shield: 3
|
||||
},
|
||||
{
|
||||
// idx: 7
|
||||
name: "monster speed-2",
|
||||
desc: "速度很快的怪物",
|
||||
speed: 30,
|
||||
max_speed: 40,
|
||||
life: 30,
|
||||
damage: 4, // 到达终点后会带来多少点伤害(1 ~ 10)
|
||||
shield: 1
|
||||
},
|
||||
{
|
||||
// idx: 8
|
||||
name: "monster shield-life",
|
||||
desc: "防御很强、生命值很高的怪物",
|
||||
speed: 3,
|
||||
max_speed: 10,
|
||||
life: 300,
|
||||
damage: 5, // 到达终点后会带来多少点伤害(1 ~ 10)
|
||||
shield: 15
|
||||
}
|
||||
];
|
||||
|
||||
if (typeof monster_idx == "undefined") {
|
||||
// 如果只传了一个参数,则只返回共定义了多少种怪物(供 td.js 中使用)
|
||||
return monster_attributes.length;
|
||||
}
|
||||
|
||||
var attr = monster_attributes[monster_idx] || monster_attributes[0],
|
||||
attr2 = {};
|
||||
|
||||
TD.lang.mix(attr2, attr);
|
||||
if (!attr2.render) {
|
||||
// 如果没有指定当前怪物的渲染方法
|
||||
attr2.render = defaultMonsterRender
|
||||
}
|
||||
|
||||
return attr2;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 生成一个怪物列表,
|
||||
* 包含 n 个怪物
|
||||
* 怪物类型在 range 中指定,如未指定,则为随机
|
||||
*/
|
||||
TD.makeMonsters = function (n, range) {
|
||||
var a = [], count = 0, i, c, d, r, l = TD.monster_type_count;
|
||||
if (!range) {
|
||||
range = [];
|
||||
for (i = 0; i < l; i++) {
|
||||
range.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
while (count < n) {
|
||||
d = n - count;
|
||||
c = Math.min(
|
||||
Math.floor(Math.random() * d) + 1,
|
||||
3 // 同一类型的怪物一次最多出现 3 个,防止某一波中怪出大量高防御或高速度的怪
|
||||
);
|
||||
r = Math.floor(Math.random() * l);
|
||||
a.push([c, range[r]]);
|
||||
count += c;
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
|
||||
}); // _TD.a.push end
|
||||
313
html5-tower-defense-master/src/js/td-data-stage-1.js
Normal file
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* 默认关卡
|
||||
*/
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
// main stage 初始化方法
|
||||
var _stage_main_init = function () {
|
||||
var act = new TD.Act(this, "act-1"),
|
||||
scene = new TD.Scene(act, "scene-1"),
|
||||
cfg = TD.getDefaultStageData("scene_endless");
|
||||
|
||||
this.config = cfg.config;
|
||||
TD.life = this.config.life;
|
||||
TD.money = this.config.money;
|
||||
TD.score = this.config.score;
|
||||
TD.difficulty = this.config.difficulty;
|
||||
TD.wave_damage = this.config.wave_damage;
|
||||
|
||||
// make map
|
||||
var map = new TD.Map("main-map", TD.lang.mix({
|
||||
scene: scene,
|
||||
is_main_map: true,
|
||||
step_level: 1,
|
||||
render_level: 2
|
||||
}, cfg.map));
|
||||
map.addToScene(scene, 1, 2, map.grids);
|
||||
scene.map = map;
|
||||
|
||||
// make panel
|
||||
scene.panel = new TD.Panel("panel", TD.lang.mix({
|
||||
scene: scene,
|
||||
main_map: map,
|
||||
step_level: 1,
|
||||
render_level: 7
|
||||
}, cfg.panel));
|
||||
|
||||
this.newWave = cfg.newWave;
|
||||
this.map = map;
|
||||
this.wait_new_wave = this.config.wait_new_wave;
|
||||
},
|
||||
_stage_main_step2 = function () {
|
||||
//TD.log(this.current_act.current_scene.wave);
|
||||
|
||||
var scene = this.current_act.current_scene,
|
||||
wave = scene.wave;
|
||||
if ((wave == 0 && !this.map.has_weapon) || scene.state != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.map.monsters.length == 0) {
|
||||
if (wave > 0 && this.wait_new_wave == this.config.wait_new_wave - 1) {
|
||||
// 一波怪物刚刚走完
|
||||
// 奖励生命值
|
||||
|
||||
var wave_reward = 0;
|
||||
if (wave % 10 == 0) {
|
||||
wave_reward = 10;
|
||||
} else if (wave % 5 == 0) {
|
||||
wave_reward = 5;
|
||||
}
|
||||
if (TD.life + wave_reward > 100) {
|
||||
wave_reward = 100 - TD.life;
|
||||
}
|
||||
if (wave_reward > 0) {
|
||||
TD.recover(wave_reward);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.wait_new_wave > 0) {
|
||||
this.wait_new_wave--;
|
||||
return;
|
||||
}
|
||||
|
||||
this.wait_new_wave = this.config.wait_new_wave;
|
||||
wave++;
|
||||
scene.wave = wave;
|
||||
this.newWave({
|
||||
map: this.map,
|
||||
wave: wave
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
TD.getDefaultStageData = function (k) {
|
||||
var data = {
|
||||
stage_main: {
|
||||
width: 900 * _TD.retina, // px (增加到 900 以容纳更大的棋盘和控制面板)
|
||||
height: 580 * _TD.retina,
|
||||
init: _stage_main_init,
|
||||
step2: _stage_main_step2
|
||||
},
|
||||
|
||||
scene_endless: {
|
||||
// scene 1
|
||||
map: {
|
||||
grid_x: 21,
|
||||
grid_y: 17,
|
||||
x: TD.padding,
|
||||
y: TD.padding,
|
||||
entrance: [0, 0],
|
||||
exit: [20, 16],
|
||||
grids_cfg: [
|
||||
{
|
||||
pos: [3, 3],
|
||||
//building: "cannon",
|
||||
passable_flag: 0
|
||||
},
|
||||
{
|
||||
pos: [7, 15],
|
||||
build_flag: 0
|
||||
},
|
||||
{
|
||||
pos: [4, 12],
|
||||
building: "wall"
|
||||
},
|
||||
{
|
||||
pos: [4, 13],
|
||||
building: "wall"
|
||||
//}, {
|
||||
//pos: [11, 9],
|
||||
//building: "cannon"
|
||||
//}, {
|
||||
//pos: [5, 2],
|
||||
//building: "HMG"
|
||||
//}, {
|
||||
//pos: [14, 9],
|
||||
//building: "LMG"
|
||||
//}, {
|
||||
//pos: [3, 14],
|
||||
//building: "LMG"
|
||||
}
|
||||
]
|
||||
},
|
||||
panel: {
|
||||
x: TD.padding * 2 + TD.grid_size * 21,
|
||||
y: TD.padding,
|
||||
map: {
|
||||
grid_x: 3,
|
||||
grid_y: 3,
|
||||
x: 0,
|
||||
y: 110 * _TD.retina,
|
||||
grids_cfg: [
|
||||
{
|
||||
pos: [0, 0],
|
||||
building: "cannon"
|
||||
},
|
||||
{
|
||||
pos: [1, 0],
|
||||
building: "LMG"
|
||||
},
|
||||
{
|
||||
pos: [2, 0],
|
||||
building: "HMG"
|
||||
},
|
||||
{
|
||||
pos: [0, 1],
|
||||
building: "laser_gun"
|
||||
},
|
||||
{
|
||||
pos: [2, 2],
|
||||
building: "wall"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
config: {
|
||||
endless: true,
|
||||
wait_new_wave: TD.exp_fps * 3, // 经过多少 step 后再开始新的一波
|
||||
difficulty: 1.0, // 难度系数
|
||||
wave: 0,
|
||||
max_wave: -1,
|
||||
wave_damage: 0, // 当前一波怪物造成了多少点生命值的伤害
|
||||
max_monsters_per_wave: 100, // 每一波最多多少怪物
|
||||
money: 500,
|
||||
score: 0, // 开局时的积分
|
||||
life: 100,
|
||||
waves: [ // 这儿只定义了前 10 波怪物,从第 11 波开始自动生成
|
||||
[],
|
||||
// 第一个参数是没有用的(第 0 波)
|
||||
|
||||
// 第一波
|
||||
[
|
||||
[1, 0] // 1 个 0 类怪物
|
||||
],
|
||||
|
||||
// 第二波
|
||||
[
|
||||
[1, 0], // 1 个 0 类怪物
|
||||
[1, 1] // 1 个 1 类怪物
|
||||
],
|
||||
|
||||
// wave 3
|
||||
[
|
||||
[2, 0], // 2 个 0 类怪物
|
||||
[1, 1] // 1 个 1 类怪物
|
||||
],
|
||||
|
||||
// wave 4
|
||||
[
|
||||
[2, 0],
|
||||
[1, 1]
|
||||
],
|
||||
|
||||
// wave 5
|
||||
[
|
||||
[3, 0],
|
||||
[2, 1]
|
||||
],
|
||||
|
||||
// wave 6
|
||||
[
|
||||
[4, 0],
|
||||
[2, 1]
|
||||
],
|
||||
|
||||
// wave 7
|
||||
[
|
||||
[5, 0],
|
||||
[3, 1],
|
||||
[1, 2]
|
||||
],
|
||||
|
||||
// wave 8
|
||||
[
|
||||
[6, 0],
|
||||
[4, 1],
|
||||
[1, 2]
|
||||
],
|
||||
|
||||
// wave 9
|
||||
[
|
||||
[7, 0],
|
||||
[3, 1],
|
||||
[2, 2]
|
||||
],
|
||||
|
||||
// wave 10
|
||||
[
|
||||
[8, 0],
|
||||
[4, 1],
|
||||
[3, 2]
|
||||
]
|
||||
]
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成第 n 波怪物的方法
|
||||
*/
|
||||
newWave: function (cfg) {
|
||||
cfg = cfg || {};
|
||||
var map = cfg.map,
|
||||
wave = cfg.wave || 1,
|
||||
//difficulty = TD.difficulty || 1.0,
|
||||
wave_damage = TD.wave_damage || 0;
|
||||
|
||||
// 自动调整难度系数
|
||||
if (wave == 1) {
|
||||
//pass
|
||||
} else if (wave_damage == 0) {
|
||||
// 没有造成伤害
|
||||
if (wave < 5) {
|
||||
TD.difficulty *= 1.05;
|
||||
} else if (TD.difficulty > 30) {
|
||||
TD.difficulty *= 1.1;
|
||||
} else {
|
||||
TD.difficulty *= 1.2;
|
||||
}
|
||||
} else if (TD.wave_damage >= 50) {
|
||||
TD.difficulty *= 0.6;
|
||||
} else if (TD.wave_damage >= 30) {
|
||||
TD.difficulty *= 0.7;
|
||||
} else if (TD.wave_damage >= 20) {
|
||||
TD.difficulty *= 0.8;
|
||||
} else if (TD.wave_damage >= 10) {
|
||||
TD.difficulty *= 0.9;
|
||||
} else {
|
||||
// 造成了 10 点以内的伤害
|
||||
if (wave >= 10)
|
||||
TD.difficulty *= 1.05;
|
||||
}
|
||||
if (TD.difficulty < 1) TD.difficulty = 1;
|
||||
|
||||
TD.log("wave " + wave + ", last wave damage = " + wave_damage + ", difficulty = " + TD.difficulty);
|
||||
|
||||
//map.addMonsters(100, 7);
|
||||
//map.addMonsters2([[10, 7], [5, 0], [5, 5]]);
|
||||
//
|
||||
var wave_data = this.config.waves[wave] ||
|
||||
// 自动生成怪物
|
||||
TD.makeMonsters(Math.min(
|
||||
Math.floor(Math.pow(wave, 1.1)),
|
||||
this.config.max_monsters_per_wave
|
||||
));
|
||||
map.addMonsters2(wave_data);
|
||||
|
||||
TD.wave_damage = 0;
|
||||
}
|
||||
} // end of scene_endless
|
||||
};
|
||||
|
||||
return data[k] || {};
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
|
||||
|
||||
151
html5-tower-defense-master/src/js/td-element.js
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
*
|
||||
* 本文件定义了 Element 类,这个类是游戏中所有元素的基类,
|
||||
* 包括地图、格子、怪物、建筑、子弹、气球提示等都基于这个类
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
/**
|
||||
* Element 是游戏中所有可控制元素的基类
|
||||
* @param id {String} 给这个元素一个唯一的不重复的 ID,如果不指定则随机生成
|
||||
* @param cfg {Object} 元素的配置信息
|
||||
*/
|
||||
TD.Element = function (id, cfg) {
|
||||
this.id = id || ("el-" + TD.lang.rndStr());
|
||||
this.cfg = cfg || {};
|
||||
|
||||
this.is_valid = true;
|
||||
this.is_visiable = typeof cfg.is_visiable != "undefined" ? cfg.is_visiable : true;
|
||||
this.is_paused = false;
|
||||
this.is_hover = false;
|
||||
this.x = this.cfg.x || -1;
|
||||
this.y = this.cfg.y || -1;
|
||||
this.width = this.cfg.width || 0;
|
||||
this.height = this.cfg.height || 0;
|
||||
this.step_level = cfg.step_level || 1;
|
||||
this.render_level = cfg.render_level;
|
||||
this.on_events = cfg.on_events || [];
|
||||
|
||||
this._init();
|
||||
};
|
||||
|
||||
TD.Element.prototype = {
|
||||
_init: function () {
|
||||
var _this = this,
|
||||
i, en, len;
|
||||
|
||||
// 监听指定事件
|
||||
for (i = 0, len = this.on_events.length; i < len; i++) {
|
||||
en = this.on_events[i];
|
||||
switch (en) {
|
||||
|
||||
// 鼠标进入元素
|
||||
case "enter":
|
||||
this.on("enter", function () {
|
||||
_this.onEnter();
|
||||
});
|
||||
break;
|
||||
|
||||
// 鼠标移出元素
|
||||
case "out":
|
||||
this.on("out", function () {
|
||||
_this.onOut();
|
||||
});
|
||||
break;
|
||||
|
||||
// 鼠标在元素上,相当于 DOM 中的 onmouseover
|
||||
case "hover":
|
||||
this.on("hover", function () {
|
||||
_this.onHover();
|
||||
});
|
||||
break;
|
||||
|
||||
// 鼠标点击了元素
|
||||
case "click":
|
||||
this.on("click", function () {
|
||||
_this.onClick();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.caculatePos();
|
||||
},
|
||||
/**
|
||||
* 重新计算元素的位置信息
|
||||
*/
|
||||
caculatePos: function () {
|
||||
this.cx = this.x + this.width / 2; // 中心的位置
|
||||
this.cy = this.y + this.height / 2;
|
||||
this.x2 = this.x + this.width; // 右边界
|
||||
this.y2 = this.y + this.height; // 下边界
|
||||
},
|
||||
start: function () {
|
||||
this.is_paused = false;
|
||||
},
|
||||
pause: function () {
|
||||
this.is_paused = true;
|
||||
},
|
||||
hide: function () {
|
||||
this.is_visiable = false;
|
||||
this.onOut();
|
||||
},
|
||||
show: function () {
|
||||
this.is_visiable = true;
|
||||
},
|
||||
/**
|
||||
* 删除本元素
|
||||
*/
|
||||
del: function () {
|
||||
this.is_valid = false;
|
||||
},
|
||||
/**
|
||||
* 绑定指定类型的事件
|
||||
* @param evt_type {String} 事件类型
|
||||
* @param f {Function} 处理方法
|
||||
*/
|
||||
on: function (evt_type, f) {
|
||||
TD.eventManager.on(this, evt_type, f);
|
||||
},
|
||||
|
||||
// 下面几个方法默认为空,实例中按需要重载
|
||||
onEnter: TD.lang.nullFunc,
|
||||
onOut: TD.lang.nullFunc,
|
||||
onHover: TD.lang.nullFunc,
|
||||
onClick: TD.lang.nullFunc,
|
||||
step: TD.lang.nullFunc,
|
||||
render: TD.lang.nullFunc,
|
||||
|
||||
/**
|
||||
* 将当前 element 加入到场景 scene 中
|
||||
* 在加入本 element 之前,先加入 pre_add_list 中的element
|
||||
* @param scene
|
||||
* @param step_level {Number}
|
||||
* @param render_level {Number}
|
||||
* @param pre_add_list {Array} Optional [element1, element2, ...]
|
||||
*/
|
||||
addToScene: function (scene, step_level, render_level, pre_add_list) {
|
||||
this.scene = scene;
|
||||
if (isNaN(step_level)) return;
|
||||
this.step_level = step_level || this.step_level;
|
||||
this.render_level = render_level || this.render_level;
|
||||
|
||||
if (pre_add_list) {
|
||||
TD.lang.each(pre_add_list, function (obj) {
|
||||
scene.addElement(obj, step_level, render_level);
|
||||
});
|
||||
}
|
||||
scene.addElement(this, step_level, render_level);
|
||||
}
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
|
||||
186
html5-tower-defense-master/src/js/td-event.js
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* Last Update: 2011/1/10 5:22:52
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
/**
|
||||
* 事件管理器
|
||||
*/
|
||||
TD.eventManager = {
|
||||
ex: -1, // 事件坐标 x
|
||||
ey: -1, // 事件坐标 y
|
||||
_registers: {}, // 注册监听事件的元素
|
||||
|
||||
// 目前支持的事件类型
|
||||
ontypes: [
|
||||
"enter", // 鼠标移入
|
||||
"hover", // 鼠标在元素上,相当于 onmouseover
|
||||
"out", // 鼠标移出
|
||||
"click" // 鼠标点击
|
||||
],
|
||||
|
||||
// 当前事件类型
|
||||
current_type: "hover",
|
||||
|
||||
/**
|
||||
* 根据事件坐标,判断事件是否在元素上
|
||||
* @param el {Element} Element 元素
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isOn: function (el) {
|
||||
return (this.ex != -1 &&
|
||||
this.ey != -1 &&
|
||||
this.ex > el.x &&
|
||||
this.ex < el.x2 &&
|
||||
this.ey > el.y &&
|
||||
this.ey < el.y2);
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据元素名、事件名,生成一个字符串标识,用于注册事件监听
|
||||
* @param el {Element}
|
||||
* @param evt_type {String}
|
||||
* @return evt_name {String} 字符串标识
|
||||
*/
|
||||
_mkElEvtName: function (el, evt_type) {
|
||||
return el.id + "::_evt_::" + evt_type;
|
||||
},
|
||||
|
||||
/**
|
||||
* 为元素注册事件监听
|
||||
* 现在的实现比较简单,如果一个元素对某个事件多次注册监听,后面的监听将会覆盖前面的
|
||||
*
|
||||
* @param el {Element}
|
||||
* @param evt_type {String}
|
||||
* @param f {Function}
|
||||
*/
|
||||
on: function (el, evt_type, f) {
|
||||
this._registers[this._mkElEvtName(el, evt_type)] = [el, evt_type, f];
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除元素对指定事件的监听
|
||||
* @param el {Element}
|
||||
* @param evt_type {String}
|
||||
*/
|
||||
removeEventListener: function (el, evt_type) {
|
||||
var en = this._mkElEvtName(el, evt_type);
|
||||
delete this._registers[en];
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除所有监听事件
|
||||
*/
|
||||
clear: function () {
|
||||
delete this._registers;
|
||||
this._registers = {};
|
||||
//this.elements = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* 主循环方法
|
||||
*/
|
||||
step: function () {
|
||||
if (!this.current_type) return; // 没有事件被触发
|
||||
|
||||
var k, a, el, et, f,
|
||||
//en,
|
||||
j,
|
||||
_this = this,
|
||||
ontypes_len = this.ontypes.length,
|
||||
is_evt_on,
|
||||
// reg_length = 0,
|
||||
to_del_el = [];
|
||||
|
||||
//var m = TD.stage.current_act.current_scene.map;
|
||||
//TD.log([m.is_hover, this.isOn(m)]);
|
||||
|
||||
// 遍历当前注册的事件
|
||||
for (k in this._registers) {
|
||||
// reg_length ++;
|
||||
if (!this._registers.hasOwnProperty(k)) continue;
|
||||
a = this._registers[k];
|
||||
el = a[0]; // 事件对应的元素
|
||||
et = a[1]; // 事件类型
|
||||
f = a[2]; // 事件处理函数
|
||||
if (!el.is_valid) {
|
||||
to_del_el.push(el);
|
||||
continue;
|
||||
}
|
||||
if (!el.is_visiable) continue; // 不可见元素不响应事件
|
||||
|
||||
is_evt_on = this.isOn(el); // 事件是否发生在元素上
|
||||
|
||||
if (this.current_type != "click") {
|
||||
// enter / out / hover 事件
|
||||
|
||||
if (et == "hover" && el.is_hover && is_evt_on) {
|
||||
// 普通的 hover
|
||||
f();
|
||||
this.current_type = "hover";
|
||||
} else if (et == "enter" && !el.is_hover && is_evt_on) {
|
||||
// enter 事件
|
||||
el.is_hover = true;
|
||||
f();
|
||||
this.current_type = "enter";
|
||||
} else if (et == "out" && el.is_hover && !is_evt_on) {
|
||||
// out 事件
|
||||
el.is_hover = false;
|
||||
f();
|
||||
this.current_type = "out";
|
||||
// } else {
|
||||
// 事件与当前元素无关
|
||||
// continue;
|
||||
}
|
||||
|
||||
} else {
|
||||
// click 事件
|
||||
if (is_evt_on && et == "click") f();
|
||||
}
|
||||
}
|
||||
|
||||
// 删除指定元素列表的事件
|
||||
TD.lang.each(to_del_el, function (obj) {
|
||||
for (j = 0; j < ontypes_len; j++)
|
||||
_this.removeEventListener(obj, _this.ontypes[j]);
|
||||
});
|
||||
// TD.log(reg_length);
|
||||
this.current_type = "";
|
||||
},
|
||||
|
||||
/**
|
||||
* 鼠标在元素上
|
||||
* @param ex {Number}
|
||||
* @param ey {Number}
|
||||
*/
|
||||
hover: function (ex, ey) {
|
||||
// 如果还有 click 事件未处理则退出,点击事件具有更高的优先级
|
||||
if (this.current_type == "click") return;
|
||||
|
||||
this.current_type = "hover";
|
||||
this.ex = ex;
|
||||
this.ey = ey;
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击事件
|
||||
* @param ex {Number}
|
||||
* @param ey {Number}
|
||||
*/
|
||||
click: function (ex, ey) {
|
||||
this.current_type = "click";
|
||||
this.ex = ex;
|
||||
this.ey = ey;
|
||||
}
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
|
||||
229
html5-tower-defense-master/src/js/td-lang.js
Normal file
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* Last Update: 2011/1/10 5:22:52
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
TD.lang = {
|
||||
/**
|
||||
* document.getElementById 方法的简写
|
||||
* @param el_id {String}
|
||||
*/
|
||||
$e: function (el_id) {
|
||||
return document.getElementById(el_id);
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建一个 DOM 元素
|
||||
* @param tag_name {String}
|
||||
* @param attributes {Object}
|
||||
* @param parent_node {HTMLElement}
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
$c: function (tag_name, attributes, parent_node) {
|
||||
var el = document.createElement(tag_name);
|
||||
attributes = attributes || {};
|
||||
|
||||
for (var k in attributes) {
|
||||
if (attributes.hasOwnProperty(k)) {
|
||||
el.setAttribute(k, attributes[k]);
|
||||
}
|
||||
}
|
||||
|
||||
if (parent_node)
|
||||
parent_node.appendChild(el);
|
||||
|
||||
return el;
|
||||
},
|
||||
|
||||
/**
|
||||
* 从字符串 s 左边截取n个字符
|
||||
* 如果包含汉字,则汉字按两个字符计算
|
||||
* @param s {String} 输入的字符串
|
||||
* @param n {Number}
|
||||
*/
|
||||
strLeft: function (s, n) {
|
||||
var s2 = s.slice(0, n),
|
||||
i = s2.replace(/[^\x00-\xff]/g, "**").length;
|
||||
if (i <= n) return s2;
|
||||
i -= s2.length;
|
||||
switch (i) {
|
||||
case 0:
|
||||
return s2;
|
||||
case n:
|
||||
return s.slice(0, n >> 1);
|
||||
default:
|
||||
var k = n - i,
|
||||
s3 = s.slice(k, n),
|
||||
j = s3.replace(/[\x00-\xff]/g, "").length;
|
||||
return j ?
|
||||
s.slice(0, k) + this.arguments.callee(s3, j) :
|
||||
s.slice(0, k);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 取得一个字符串的字节长度
|
||||
* 汉字等字符长度算2,英文、数字等算1
|
||||
* @param s {String}
|
||||
*/
|
||||
strLen2: function (s) {
|
||||
return s.replace(/[^\x00-\xff]/g, "**").length;
|
||||
},
|
||||
|
||||
/**
|
||||
* 对一个数组的每一个元素执行指定方法
|
||||
* @param list {Array}
|
||||
* @param f {Function}
|
||||
*/
|
||||
each: function (list, f) {
|
||||
if (Array.prototype.forEach) {
|
||||
list.forEach(f);
|
||||
} else {
|
||||
for (var i = 0, l = list.length; i < l; i++) {
|
||||
f(list[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 对一个数组的每一项依次执行指定方法,直到某一项的返回值为 true
|
||||
* 返回第一个令 f 值为 true 的元素,如没有元素令 f 值为 true,则
|
||||
* 返回 null
|
||||
* @param list {Array}
|
||||
* @param f {Function}
|
||||
* @return {Object}
|
||||
*/
|
||||
any: function (list, f) {
|
||||
for (var i = 0, l = list.length; i < l; i++) {
|
||||
if (f(list[i]))
|
||||
return list[i];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* 依次弹出列表中的元素,并对其进行操作
|
||||
* 注意,执行完毕之后原数组将被清空
|
||||
* 类似于 each,不同的是这个函数执行完后原数组将被清空
|
||||
* @param list {Array}
|
||||
* @param f {Function}
|
||||
*/
|
||||
shift: function (list, f) {
|
||||
while (list[0]) {
|
||||
f(list.shift());
|
||||
// f.apply(list.shift(), args);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入一个数组,将其随机排序并返回
|
||||
* 返回的是一个新的数组,原数组不变
|
||||
* @param list {Array}
|
||||
* @return {Array}
|
||||
*/
|
||||
rndSort: function (list) {
|
||||
var a = list.concat();
|
||||
return a.sort(function () {
|
||||
return Math.random() - 0.5;
|
||||
});
|
||||
},
|
||||
|
||||
_rndRGB2: function (v) {
|
||||
var s = v.toString(16);
|
||||
return s.length == 2 ? s : ("0" + s);
|
||||
},
|
||||
/**
|
||||
* 随机生成一个 RGB 颜色
|
||||
*/
|
||||
rndRGB: function () {
|
||||
var r = Math.floor(Math.random() * 256),
|
||||
g = Math.floor(Math.random() * 256),
|
||||
b = Math.floor(Math.random() * 256);
|
||||
|
||||
return "#" + this._rndRGB2(r) + this._rndRGB2(g) + this._rndRGB2(b);
|
||||
},
|
||||
/**
|
||||
* 将一个 rgb 色彩字符串转化为一个数组
|
||||
* eg: '#ffffff' => [255, 255, 255]
|
||||
* @param rgb_str {String} rgb色彩字符串,类似于“#f8c693”
|
||||
*/
|
||||
rgb2Arr: function (rgb_str) {
|
||||
if (rgb_str.length != 7) return [0, 0, 0];
|
||||
|
||||
var r = rgb_str.substr(1, 2),
|
||||
g = rgb_str.substr(3, 2),
|
||||
b = rgb_str.substr(3, 2);
|
||||
|
||||
return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)];
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成一个长度为 n 的随机字符串
|
||||
*
|
||||
* @param [n] {Number}
|
||||
*/
|
||||
rndStr: function (n) {
|
||||
n = n || 16;
|
||||
var chars = "1234567890abcdefghijklmnopqrstuvwxyz",
|
||||
a = [],
|
||||
i, chars_len = chars.length, r;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
r = Math.floor(Math.random() * chars_len);
|
||||
a.push(chars.substr(r, 1));
|
||||
}
|
||||
return a.join("");
|
||||
},
|
||||
|
||||
/**
|
||||
* 空函数,一般用于占位
|
||||
*/
|
||||
nullFunc: function () {
|
||||
},
|
||||
|
||||
/**
|
||||
* 判断两个数组是否相等
|
||||
*
|
||||
* @param arr1 {Array}
|
||||
* @param arr2 {Array}
|
||||
*/
|
||||
arrayEqual: function (arr1, arr2) {
|
||||
var i, l = arr1.length;
|
||||
|
||||
if (l != arr2.length) return false;
|
||||
|
||||
for (i = 0; i < l; i++) {
|
||||
if (arr1[i] != arr2[i]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* 将所有 s 的属性复制给 r
|
||||
* @param r {Object}
|
||||
* @param s {Object}
|
||||
* @param [is_overwrite] {Boolean} 如指定为 false ,则不覆盖已有的值,其它值
|
||||
* 包括 undefined ,都表示 s 中的同名属性将覆盖 r 中的值
|
||||
*/
|
||||
mix: function (r, s, is_overwrite) {
|
||||
if (!s || !r) return r;
|
||||
|
||||
for (var p in s) {
|
||||
if (s.hasOwnProperty(p) && (is_overwrite !== false || !(p in r))) {
|
||||
r[p] = s[p];
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
70
html5-tower-defense-master/src/js/td-msg-en.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* Last Update: 2011/1/10 5:22:52
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
TD._msg_texts = {
|
||||
"_cant_build": "Can't build here!",
|
||||
"_cant_pass": "Can't pass!",
|
||||
"entrance": "Entrance",
|
||||
"exit": "Exit",
|
||||
"not_enough_money": "Not enough money, need $${0}.",
|
||||
"wave_info": "Wave ${0}",
|
||||
"panel_money_title": "Money: ",
|
||||
"panel_score_title": "Score: ",
|
||||
"panel_life_title": "Life: ",
|
||||
"panel_building_title": "Buildings: ",
|
||||
"panel_monster_title": "Monsters: ",
|
||||
"building_name_wall": "Roadblock",
|
||||
"building_name_cannon": "Cannon",
|
||||
"building_name_LMG": "LMG",
|
||||
"building_name_HMG": "HMG",
|
||||
"building_name_laser_gun": "Laser gun",
|
||||
"building_info": "${0}: Level ${1}, Damage ${2}, Speed ${3}, Range ${4}, Kill ${5}",
|
||||
"building_info_wall": "${0}",
|
||||
"building_intro_wall": "Roadblock: monsters could not pass ($${0})",
|
||||
"building_intro_cannon": "Cannon: blance in range and damage ($${0})",
|
||||
"building_intro_LMG": "Light Machine Gun: longer range, normal damage ($${0})",
|
||||
"building_intro_HMG": "Heavy Machine Gun: fast shoot, greater damage, normal range ($${0})",
|
||||
"building_intro_laser_gun": "Laser gun: greater damage, 100% hit ($${0})",
|
||||
"click_to_build": "Left click to build ${0} ($${1})",
|
||||
"upgrade": "Upgrade ${0} to level ${1} , cost $${2}。",
|
||||
"sell": "Sell ${0} for $${1}",
|
||||
"upgrade_success": "Upgrade success! ${0} upgrades to level ${1}. Next upgrade will take $${2}.",
|
||||
"monster_info": "Monster: Life ${0}, Shield ${1}, Speed ${2}, Damage ${3}",
|
||||
"button_upgrade_text": "Upgrade",
|
||||
"button_sell_text": "Sell",
|
||||
"button_start_text": "Start",
|
||||
"button_restart_text": "Restart",
|
||||
"button_pause_text": "Pause",
|
||||
"button_continue_text": "Continue",
|
||||
"button_pause_desc_0": "Pause the game",
|
||||
"button_pause_desc_1": "Resume the game",
|
||||
"blocked": "Can't build here, it will block the way from entrance to exit!",
|
||||
"monster_be_blocked": "Can't build here, some monster will be blocked!",
|
||||
"entrance_or_exit_be_blocked": "Can't build on the entrance or the exit!",
|
||||
"_": "ERROR"
|
||||
};
|
||||
|
||||
TD._t = TD.translate = function (k, args) {
|
||||
args = (typeof args == "object" && args.constructor == Array) ? args : [];
|
||||
var msg = this._msg_texts[k] || this._msg_texts["_"],
|
||||
i,
|
||||
l = args.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
msg = msg.replace("${" + i + "}", args[i]);
|
||||
}
|
||||
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
}); // _TD.a.push end
|
||||
70
html5-tower-defense-master/src/js/td-msg-zh.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* Last Update: 2011/1/10 5:22:52
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
TD._msg_texts = {
|
||||
"_cant_build": "不能在这儿修建",
|
||||
"_cant_pass": "怪物不能通过这儿",
|
||||
"entrance": "起点",
|
||||
"exit": "终点",
|
||||
"not_enough_money": "金钱不足,需要 $${0}!",
|
||||
"wave_info": "第 ${0} 波",
|
||||
"panel_money_title": "金钱: ",
|
||||
"panel_score_title": "积分: ",
|
||||
"panel_life_title": "生命: ",
|
||||
"panel_building_title": "建筑: ",
|
||||
"panel_monster_title": "怪物: ",
|
||||
"building_name_wall": "路障",
|
||||
"building_name_cannon": "炮台",
|
||||
"building_name_LMG": "轻机枪",
|
||||
"building_name_HMG": "重机枪",
|
||||
"building_name_laser_gun": "激光炮",
|
||||
"building_info": "${0}: 等级 ${1},攻击 ${2},速度 ${3},射程 ${4},战绩 ${5}",
|
||||
"building_info_wall": "${0}",
|
||||
"building_intro_wall": "路障 可以阻止怪物通过 ($${0})",
|
||||
"building_intro_cannon": "炮台 射程、杀伤力较为平衡 ($${0})",
|
||||
"building_intro_LMG": "轻机枪 射程较远,杀伤力一般 ($${0})",
|
||||
"building_intro_HMG": "重机枪 快速射击,威力较大,射程一般 ($${0})",
|
||||
"building_intro_laser_gun": "激光枪 伤害较大,命中率 100% ($${0})",
|
||||
"click_to_build": "左键点击建造 ${0} ($${1})",
|
||||
"upgrade": "升级 ${0} 到 ${1} 级,需花费 $${2}。",
|
||||
"sell": "出售 ${0},可获得 $${1}",
|
||||
"upgrade_success": "升级成功,${0} 已升级到 ${1} 级!下次升级需要 $${2}。",
|
||||
"monster_info": "怪物: 生命 ${0},防御 ${1},速度 ${2},伤害 ${3}",
|
||||
"button_upgrade_text": "升级",
|
||||
"button_sell_text": "出售",
|
||||
"button_start_text": "开始",
|
||||
"button_restart_text": "重新开始",
|
||||
"button_pause_text": "暂停",
|
||||
"button_continue_text": "继续",
|
||||
"button_pause_desc_0": "游戏暂停",
|
||||
"button_pause_desc_1": "游戏继续",
|
||||
"blocked": "不能在这儿修建建筑,起点与终点之间至少要有一条路可到达!",
|
||||
"monster_be_blocked": "不能在这儿修建建筑,有怪物被围起来了!",
|
||||
"entrance_or_exit_be_blocked": "不能在起点或终点处修建建筑!",
|
||||
"_": "ERROR"
|
||||
};
|
||||
|
||||
TD._t = TD.translate = function (k, args) {
|
||||
args = (typeof args == "object" && args.constructor == Array) ? args : [];
|
||||
var msg = this._msg_texts[k] || this._msg_texts["_"],
|
||||
i,
|
||||
l = args.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
msg = msg.replace("${" + i + "}", args[i]);
|
||||
}
|
||||
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
}); // _TD.a.push end
|
||||
605
html5-tower-defense-master/src/js/td-obj-building.js
Normal file
@@ -0,0 +1,605 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* Last Update: 2011/1/10 5:22:52
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
// building 对象的属性、方法。注意属性中不要有数组、对象等
|
||||
// 引用属性,否则多个实例的相关属性会发生冲突
|
||||
var building_obj = {
|
||||
_init: function (cfg) {
|
||||
this.is_selected = false;
|
||||
this.level = 0;
|
||||
this.killed = 0; // 当前建筑杀死了多少怪物
|
||||
this.target = null;
|
||||
|
||||
cfg = cfg || {};
|
||||
this.map = cfg.map || null;
|
||||
this.grid = cfg.grid || null;
|
||||
|
||||
/**
|
||||
* 子弹类型,可以有以下类型:
|
||||
* 1:普通子弹
|
||||
* 2:激光类,发射后马上命中,暂未实现
|
||||
* 3:导弹类,击中后会爆炸,带来面攻击,暂未实现
|
||||
*/
|
||||
this.bullet_type = cfg.bullet_type || 1;
|
||||
|
||||
/**
|
||||
* type 可能的值有:
|
||||
* "wall": 墙壁,没有攻击性
|
||||
* "cannon": 炮台
|
||||
* "LMG": 轻机枪
|
||||
* "HMG": 重机枪
|
||||
* "laser_gun": 激光枪
|
||||
*
|
||||
*/
|
||||
this.type = cfg.type;
|
||||
|
||||
this.speed = cfg.speed;
|
||||
this.bullet_speed = cfg.bullet_speed;
|
||||
this.is_pre_building = !!cfg.is_pre_building;
|
||||
this.blink = this.is_pre_building;
|
||||
this.wait_blink = this._default_wait_blink = 20;
|
||||
this.is_weapon = (this.type != "wall"); // 墙等不可攻击的建筑此项为 false ,其余武器此项为 true
|
||||
|
||||
var o = TD.getDefaultBuildingAttributes(this.type);
|
||||
TD.lang.mix(this, o);
|
||||
this.range_px = this.range * TD.grid_size;
|
||||
this.money = this.cost; // 购买、升级本建筑已花费的钱
|
||||
|
||||
this.caculatePos();
|
||||
},
|
||||
|
||||
/**
|
||||
* 升级本建筑需要的花费
|
||||
*/
|
||||
getUpgradeCost: function () {
|
||||
return Math.floor(this.money * 0.75);
|
||||
},
|
||||
|
||||
/**
|
||||
* 出售本建筑能得到多少钱
|
||||
*/
|
||||
getSellMoney: function () {
|
||||
return Math.floor(this.money * 0.5) || 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换选中 / 未选中状态
|
||||
*/
|
||||
toggleSelected: function () {
|
||||
this.is_selected = !this.is_selected;
|
||||
this.grid.hightLight(this.is_selected); // 高亮
|
||||
var _this = this;
|
||||
|
||||
if (this.is_selected) {
|
||||
// 如果当前建筑被选中
|
||||
|
||||
this.map.eachBuilding(function (obj) {
|
||||
obj.is_selected = obj == _this;
|
||||
});
|
||||
// 取消另一个地图中选中建筑的选中状态
|
||||
(
|
||||
this.map.is_main_map ? this.scene.panel_map : this.scene.map
|
||||
).eachBuilding(function (obj) {
|
||||
obj.is_selected = false;
|
||||
obj.grid.hightLight(false);
|
||||
});
|
||||
this.map.selected_building = this;
|
||||
|
||||
if (!this.map.is_main_map) {
|
||||
// 在面版地图中选中了建筑,进入建筑模式
|
||||
this.scene.map.preBuild(this.type);
|
||||
} else {
|
||||
// 取消建筑模式
|
||||
this.scene.map.cancelPreBuild();
|
||||
}
|
||||
|
||||
} else {
|
||||
// 如果当前建筑切换为未选中状态
|
||||
|
||||
if (this.map.selected_building == this)
|
||||
this.map.selected_building = null;
|
||||
|
||||
if (!this.map.is_main_map) {
|
||||
// 取消建筑模式
|
||||
this.scene.map.cancelPreBuild();
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是选中 / 取消选中主地图上的建筑,显示 / 隐藏对应的操作按钮
|
||||
if (this.map.is_main_map) {
|
||||
if (this.map.selected_building) {
|
||||
this.scene.panel.btn_upgrade.show();
|
||||
this.scene.panel.btn_sell.show();
|
||||
this.updateBtnDesc();
|
||||
} else {
|
||||
this.scene.panel.btn_upgrade.hide();
|
||||
this.scene.panel.btn_sell.hide();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成、更新升级按钮的说明文字
|
||||
*/
|
||||
updateBtnDesc: function () {
|
||||
this.scene.panel.btn_upgrade.desc = TD._t(
|
||||
"upgrade", [
|
||||
TD._t("building_name_" + this.type),
|
||||
this.level + 1,
|
||||
this.getUpgradeCost()
|
||||
]);
|
||||
this.scene.panel.btn_sell.desc = TD._t(
|
||||
"sell", [
|
||||
TD._t("building_name_" + this.type),
|
||||
this.getSellMoney()
|
||||
]);
|
||||
},
|
||||
|
||||
/**
|
||||
* 将本建筑放置到一个格子中
|
||||
* @param grid {Element} 指定格子
|
||||
*/
|
||||
locate: function (grid) {
|
||||
this.grid = grid;
|
||||
this.map = grid.map;
|
||||
this.cx = this.grid.cx;
|
||||
this.cy = this.grid.cy;
|
||||
this.x = this.grid.x;
|
||||
this.y = this.grid.y;
|
||||
this.x2 = this.grid.x2;
|
||||
this.y2 = this.grid.y2;
|
||||
this.width = this.grid.width;
|
||||
this.height = this.grid.height;
|
||||
|
||||
this.px = this.x + 0.5;
|
||||
this.py = this.y + 0.5;
|
||||
|
||||
this.wait_blink = this._default_wait_blink;
|
||||
this._fire_wait = Math.floor(Math.max(2 / (this.speed * TD.global_speed), 1));
|
||||
this._fire_wait2 = this._fire_wait;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 将本建筑彻底删除
|
||||
*/
|
||||
remove: function () {
|
||||
// TD.log("remove building #" + this.id + ".");
|
||||
if (this.grid && this.grid.building && this.grid.building == this)
|
||||
this.grid.building = null;
|
||||
this.hide();
|
||||
this.del();
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算怪物当前的路径进度优先级。
|
||||
* 返回值越大,表示怪物越靠近终点、越危险。
|
||||
*/
|
||||
getMonsterEntrancePriority: function (monster) {
|
||||
if (!monster || !monster.is_valid) return -1;
|
||||
|
||||
var remaining = monster.way ? monster.way.length : 0;
|
||||
|
||||
if (monster.next_grid) {
|
||||
remaining += Math.sqrt(
|
||||
Math.pow(monster.next_grid.cx - monster.cx, 2) +
|
||||
Math.pow(monster.next_grid.cy - monster.cy, 2)
|
||||
) / TD.grid_size;
|
||||
} else if (monster.map && monster.map.exit) {
|
||||
remaining += Math.sqrt(
|
||||
Math.pow(monster.map.exit.cx - monster.cx, 2) +
|
||||
Math.pow(monster.map.exit.cy - monster.cy, 2)
|
||||
) / TD.grid_size;
|
||||
}
|
||||
|
||||
return -remaining;
|
||||
},
|
||||
|
||||
/**
|
||||
* 寻找一个目标(怪物)
|
||||
*/
|
||||
findTaget: function () {
|
||||
if (!this.is_weapon || this.is_pre_building || !this.grid) return;
|
||||
|
||||
var _this = this,
|
||||
cx = this.cx, cy = this.cy,
|
||||
range2 = Math.pow(this.range_px, 2),
|
||||
best_target = null,
|
||||
best_priority = -1,
|
||||
best_distance2 = Infinity;
|
||||
|
||||
TD.lang.each(this.map.monsters, function (obj) {
|
||||
if (!obj || !obj.is_valid) return;
|
||||
|
||||
var distance2 = Math.pow(obj.cx - cx, 2) + Math.pow(obj.cy - cy, 2);
|
||||
if (distance2 > range2) return;
|
||||
|
||||
var priority = _this.getMonsterEntrancePriority(obj);
|
||||
if (
|
||||
!best_target ||
|
||||
priority > best_priority ||
|
||||
(priority == best_priority && distance2 < best_distance2)
|
||||
) {
|
||||
best_target = obj;
|
||||
best_priority = priority;
|
||||
best_distance2 = distance2;
|
||||
}
|
||||
});
|
||||
|
||||
this.target = best_target;
|
||||
},
|
||||
|
||||
/**
|
||||
* 取得目标的坐标(相对于地图左上角)
|
||||
*/
|
||||
getTargetPosition: function () {
|
||||
if (!this.target) {
|
||||
// 以 entrance 为目标
|
||||
var grid = this.map.is_main_map ? this.map.entrance : this.grid;
|
||||
return [grid.cx, grid.cy];
|
||||
}
|
||||
return [this.target.cx, this.target.cy];
|
||||
},
|
||||
|
||||
/**
|
||||
* 向自己的目标开火
|
||||
*/
|
||||
fire: function () {
|
||||
if (!this.target || !this.target.is_valid) return;
|
||||
|
||||
if (this.type == "laser_gun") {
|
||||
// 如果是激光枪,目标立刻被击中
|
||||
this.target.beHit(this, this.damage);
|
||||
return;
|
||||
}
|
||||
|
||||
var muzzle = this.muzzle || [this.cx, this.cy], // 炮口的位置
|
||||
cx = muzzle[0],
|
||||
cy = muzzle[1];
|
||||
|
||||
new TD.Bullet(null, {
|
||||
building: this,
|
||||
damage: this.damage,
|
||||
target: this.target,
|
||||
speed: this.bullet_speed,
|
||||
x: cx,
|
||||
y: cy
|
||||
});
|
||||
},
|
||||
|
||||
tryToFire: function () {
|
||||
if (!this.is_weapon || !this.target)
|
||||
return;
|
||||
|
||||
// 动态计算发射间隔,实时支持倍速模式
|
||||
var base_fire_wait = Math.floor(Math.max(2 / this.speed, 1));
|
||||
var current_fire_wait = Math.max(Math.floor(base_fire_wait / TD.global_speed), 1);
|
||||
|
||||
// 如果速度倍数改变,更新发射间隔
|
||||
if (current_fire_wait !== this._fire_wait2) {
|
||||
this._fire_wait2 = current_fire_wait;
|
||||
}
|
||||
|
||||
this._fire_wait--;
|
||||
if (this._fire_wait > 0) {
|
||||
// return;
|
||||
} else if (this._fire_wait < 0) {
|
||||
this._fire_wait = this._fire_wait2;
|
||||
} else {
|
||||
this.fire();
|
||||
}
|
||||
},
|
||||
|
||||
_upgrade2: function (k) {
|
||||
if (!this._upgrade_records[k])
|
||||
this._upgrade_records[k] = this[k];
|
||||
var v = this._upgrade_records[k],
|
||||
mk = "max_" + k,
|
||||
uk = "_upgrade_rule_" + k,
|
||||
uf = this[uk] || TD.default_upgrade_rule;
|
||||
if (!v || isNaN(v)) return;
|
||||
|
||||
v = uf(this.level, v);
|
||||
if (this[mk] && !isNaN(this[mk]) && this[mk] < v)
|
||||
v = this[mk];
|
||||
this._upgrade_records[k] = v;
|
||||
this[k] = Math.floor(v);
|
||||
},
|
||||
|
||||
/**
|
||||
* 升级建筑
|
||||
*/
|
||||
upgrade: function () {
|
||||
if (!this._upgrade_records)
|
||||
this._upgrade_records = {};
|
||||
|
||||
var attrs = [
|
||||
// 可升级的变量
|
||||
"damage", "range", "speed", "life", "shield"
|
||||
], i, l = attrs.length;
|
||||
for (i = 0; i < l; i++)
|
||||
this._upgrade2(attrs[i]);
|
||||
this.level++;
|
||||
this.range_px = this.range * TD.grid_size;
|
||||
},
|
||||
|
||||
tryToUpgrade: function (btn) {
|
||||
var cost = this.getUpgradeCost(),
|
||||
msg = "";
|
||||
if (cost > TD.money) {
|
||||
msg = TD._t("not_enough_money", [cost]);
|
||||
} else {
|
||||
TD.money -= cost;
|
||||
this.money += cost;
|
||||
this.upgrade();
|
||||
msg = TD._t("upgrade_success", [
|
||||
TD._t("building_name_" + this.type), this.level,
|
||||
this.getUpgradeCost()
|
||||
]);
|
||||
}
|
||||
|
||||
this.updateBtnDesc();
|
||||
this.scene.panel.balloontip.msg(msg, btn);
|
||||
},
|
||||
|
||||
tryToSell: function () {
|
||||
if (!this.is_valid) return;
|
||||
|
||||
TD.money += this.getSellMoney();
|
||||
this.grid.removeBuilding();
|
||||
this.is_valid = false;
|
||||
this.map.selected_building = null;
|
||||
this.map.select_hl.hide();
|
||||
this.map.checkHasWeapon();
|
||||
this.scene.panel.btn_upgrade.hide();
|
||||
this.scene.panel.btn_sell.hide();
|
||||
this.scene.panel.balloontip.hide();
|
||||
},
|
||||
|
||||
step: function () {
|
||||
if (this.blink) {
|
||||
this.wait_blink--;
|
||||
if (this.wait_blink < -this._default_wait_blink)
|
||||
this.wait_blink = this._default_wait_blink;
|
||||
}
|
||||
|
||||
this.findTaget();
|
||||
this.tryToFire();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
if (!this.is_visiable || this.wait_blink < 0) return;
|
||||
|
||||
var ctx = TD.ctx;
|
||||
|
||||
TD.renderBuilding(this);
|
||||
|
||||
if (
|
||||
this.map.is_main_map &&
|
||||
(
|
||||
this.is_selected || (this.is_pre_building) ||
|
||||
this.map.show_all_ranges
|
||||
) &&
|
||||
this.is_weapon && this.range > 0 && this.grid
|
||||
) {
|
||||
// 画射程
|
||||
ctx.lineWidth = _TD.retina;
|
||||
ctx.fillStyle = "rgba(187, 141, 32, 0.15)";
|
||||
ctx.strokeStyle = "#bb8d20";
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.cx, this.cy, this.range_px, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (this.type == "laser_gun" && this.target && this.target.is_valid) {
|
||||
// 画激光
|
||||
ctx.lineWidth = 3 * _TD.retina;
|
||||
ctx.strokeStyle = "rgba(50, 50, 200, 0.5)";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.cx, this.cy);
|
||||
ctx.lineTo(this.target.cx, this.target.cy);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
ctx.lineWidth = _TD.retina;
|
||||
ctx.strokeStyle = "rgba(150, 150, 255, 0.5)";
|
||||
ctx.beginPath();
|
||||
ctx.lineTo(this.cx, this.cy);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
},
|
||||
|
||||
onEnter: function () {
|
||||
if (this.is_pre_building) return;
|
||||
|
||||
var msg = "建筑工事";
|
||||
if (this.map.is_main_map) {
|
||||
msg = TD._t("building_info" + (this.type == "wall" ? "_wall" : ""), [TD._t("building_name_" + this.type), this.level, this.damage, this.speed, this.range, this.killed]);
|
||||
} else {
|
||||
msg = TD._t("building_intro_" + this.type, [TD.getDefaultBuildingAttributes(this.type).cost]);
|
||||
}
|
||||
|
||||
this.scene.panel.balloontip.msg(msg, this.grid);
|
||||
},
|
||||
|
||||
onOut: function () {
|
||||
if (this.scene.panel.balloontip.el == this.grid) {
|
||||
this.scene.panel.balloontip.hide();
|
||||
}
|
||||
},
|
||||
|
||||
onClick: function () {
|
||||
if (this.is_pre_building || this.scene.state != 1) return;
|
||||
this.toggleSelected();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param id {String}
|
||||
* @param cfg {object} 配置对象
|
||||
* 至少需要包含以下项:
|
||||
* {
|
||||
* type: 建筑类型,可选的值有
|
||||
* "wall"
|
||||
* "cannon"
|
||||
* "LMG"
|
||||
* "HMG"
|
||||
* "laser_gun"
|
||||
* }
|
||||
*/
|
||||
TD.Building = function (id, cfg) {
|
||||
cfg.on_events = ["enter", "out", "click"];
|
||||
var building = new TD.Element(id, cfg);
|
||||
TD.lang.mix(building, building_obj);
|
||||
building._init(cfg);
|
||||
|
||||
return building;
|
||||
};
|
||||
|
||||
|
||||
// bullet 对象的属性、方法。注意属性中不要有数组、对象等
|
||||
// 引用属性,否则多个实例的相关属性会发生冲突
|
||||
var bullet_obj = {
|
||||
_init: function (cfg) {
|
||||
cfg = cfg || {};
|
||||
|
||||
this.speed = cfg.speed;
|
||||
this.damage = cfg.damage;
|
||||
this.target = cfg.target;
|
||||
this.cx = cfg.x;
|
||||
this.cy = cfg.y;
|
||||
this.r = cfg.r || Math.max(Math.log(this.damage), 2);
|
||||
if (this.r < 1) this.r = 1;
|
||||
if (this.r > 6) this.r = 6;
|
||||
|
||||
this.building = cfg.building || null;
|
||||
this.map = cfg.map || this.building.map;
|
||||
this.type = cfg.type || 1;
|
||||
this.color = cfg.color || "#000";
|
||||
|
||||
this.map.bullets.push(this);
|
||||
this.addToScene(this.map.scene, 1, 6);
|
||||
|
||||
if (this.type == 1) {
|
||||
this.caculate();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算子弹的一些数值
|
||||
*/
|
||||
caculate: function () {
|
||||
var sx, sy, c,
|
||||
tx = this.target.cx,
|
||||
ty = this.target.cy,
|
||||
speed;
|
||||
sx = tx - this.cx;
|
||||
sy = ty - this.cy;
|
||||
c = Math.sqrt(Math.pow(sx, 2) + Math.pow(sy, 2));
|
||||
speed = 20 * this.speed * TD.global_speed;
|
||||
this.vx = sx * speed / c;
|
||||
this.vy = sy * speed / c;
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查当前子弹是否已超出地图范围
|
||||
*/
|
||||
checkOutOfMap: function () {
|
||||
this.is_valid = !(
|
||||
this.cx < this.map.x ||
|
||||
this.cx > this.map.x2 ||
|
||||
this.cy < this.map.y ||
|
||||
this.cy > this.map.y2
|
||||
);
|
||||
|
||||
return !this.is_valid;
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查当前子弹是否击中了怪物
|
||||
*/
|
||||
checkHit: function () {
|
||||
var cx = this.cx,
|
||||
cy = this.cy,
|
||||
r = this.r * _TD.retina,
|
||||
monster = this.map.anyMonster(function (obj) {
|
||||
return Math.pow(obj.cx - cx, 2) + Math.pow(obj.cy - cy, 2) <= Math.pow(obj.r + r, 2) * 2;
|
||||
});
|
||||
|
||||
if (monster) {
|
||||
// 击中的怪物
|
||||
monster.beHit(this.building, this.damage);
|
||||
this.is_valid = false;
|
||||
|
||||
// 子弹小爆炸效果
|
||||
TD.Explode(this.id + "-explode", {
|
||||
cx: this.cx,
|
||||
cy: this.cy,
|
||||
r: this.r,
|
||||
step_level: this.step_level,
|
||||
render_level: this.render_level,
|
||||
color: this.color,
|
||||
scene: this.map.scene,
|
||||
time: 0.2
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
step: function () {
|
||||
if (this.checkOutOfMap() || this.checkHit()) return;
|
||||
|
||||
this.cx += this.vx;
|
||||
this.cy += this.vy;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var ctx = TD.ctx;
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.cx, this.cy, this.r, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param id {String} 配置对象
|
||||
* @param cfg {Object} 配置对象
|
||||
* 至少需要包含以下项:
|
||||
* {
|
||||
* x: 子弹发出的位置
|
||||
* y: 子弹发出的位置
|
||||
* speed:
|
||||
* damage:
|
||||
* target: 目标,一个 monster 对象
|
||||
* building: 所属的建筑
|
||||
* }
|
||||
* 子弹类型,可以有以下类型:
|
||||
* 1:普通子弹
|
||||
* 2:激光类,发射后马上命中
|
||||
* 3:导弹类,击中后会爆炸,带来面攻击
|
||||
*/
|
||||
TD.Bullet = function (id, cfg) {
|
||||
var bullet = new TD.Element(id, cfg);
|
||||
TD.lang.mix(bullet, bullet_obj);
|
||||
bullet._init(cfg);
|
||||
|
||||
return bullet;
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
287
html5-tower-defense-master/src/js/td-obj-grid.js
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* Last Update: 2011/1/10 5:22:52
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
// grid 对象的属性、方法。注意属性中不要有数组、对象等
|
||||
// 引用属性,否则多个实例的相关属性会发生冲突
|
||||
var grid_obj = {
|
||||
_init: function (cfg) {
|
||||
cfg = cfg || {};
|
||||
this.map = cfg.map;
|
||||
this.scene = this.map.scene;
|
||||
this.mx = cfg.mx; // 在 map 中的格子坐标
|
||||
this.my = cfg.my;
|
||||
this.width = TD.grid_size;
|
||||
this.height = TD.grid_size;
|
||||
this.is_entrance = this.is_exit = false;
|
||||
this.passable_flag = 1; // 0: 不可通过; 1: 可通过
|
||||
this.build_flag = 1;// 0: 不可修建; 1: 可修建; 2: 已修建
|
||||
this.building = null;
|
||||
this.caculatePos();
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据 map 位置及本 grid 的 (mx, my) ,计算格子的位置
|
||||
*/
|
||||
caculatePos: function () {
|
||||
this.x = this.map.x + this.mx * TD.grid_size;
|
||||
this.y = this.map.y + this.my * TD.grid_size;
|
||||
this.x2 = this.x + TD.grid_size;
|
||||
this.y2 = this.y + TD.grid_size;
|
||||
this.cx = Math.floor(this.x + TD.grid_size / 2);
|
||||
this.cy = Math.floor(this.y + TD.grid_size / 2);
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查如果在当前格子建东西,是否会导致起点与终点被阻塞
|
||||
*/
|
||||
checkBlock: function () {
|
||||
if (this.is_entrance || this.is_exit) {
|
||||
this._block_msg = TD._t("entrance_or_exit_be_blocked");
|
||||
return true;
|
||||
}
|
||||
|
||||
var is_blocked,
|
||||
_this = this,
|
||||
fw = new TD.FindWay(
|
||||
this.map.grid_x, this.map.grid_y,
|
||||
this.map.entrance.mx, this.map.entrance.my,
|
||||
this.map.exit.mx, this.map.exit.my,
|
||||
function (x, y) {
|
||||
return !(x == _this.mx && y == _this.my) && _this.map.checkPassable(x, y);
|
||||
}
|
||||
);
|
||||
|
||||
is_blocked = fw.is_blocked;
|
||||
|
||||
if (!is_blocked) {
|
||||
is_blocked = !!this.map.anyMonster(function (obj) {
|
||||
return obj.chkIfBlocked(_this.mx, _this.my);
|
||||
});
|
||||
if (is_blocked)
|
||||
this._block_msg = TD._t("monster_be_blocked");
|
||||
} else {
|
||||
this._block_msg = TD._t("blocked");
|
||||
}
|
||||
|
||||
return is_blocked;
|
||||
},
|
||||
|
||||
/**
|
||||
* 购买建筑
|
||||
* @param building_type {String}
|
||||
*/
|
||||
buyBuilding: function (building_type) {
|
||||
var cost = TD.getDefaultBuildingAttributes(building_type).cost || 0;
|
||||
if (TD.money >= cost) {
|
||||
TD.money -= cost;
|
||||
this.addBuilding(building_type);
|
||||
} else {
|
||||
TD.log(TD._t("not_enough_money", [cost]));
|
||||
this.scene.panel.balloontip.msg(TD._t("not_enough_money", [cost]), this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 在当前格子添加指定类型的建筑
|
||||
* @param building_type {String}
|
||||
*/
|
||||
addBuilding: function (building_type) {
|
||||
if (this.building) {
|
||||
// 如果当前格子已经有建筑,先将其移除
|
||||
this.removeBuilding();
|
||||
}
|
||||
|
||||
var building = new TD.Building("building-" + building_type + "-" + TD.lang.rndStr(), {
|
||||
type: building_type,
|
||||
step_level: this.step_level,
|
||||
render_level: this.render_level
|
||||
});
|
||||
building.locate(this);
|
||||
|
||||
this.scene.addElement(building, this.step_level, this.render_level + 1);
|
||||
this.map.buildings.push(building);
|
||||
this.building = building;
|
||||
this.build_flag = 2;
|
||||
this.map.checkHasWeapon();
|
||||
if (this.map.pre_building)
|
||||
this.map.pre_building.hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除当前格子的建筑
|
||||
*/
|
||||
removeBuilding: function () {
|
||||
if (this.build_flag == 2)
|
||||
this.build_flag = 1;
|
||||
if (this.building)
|
||||
this.building.remove();
|
||||
this.building = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* 在当前建筑添加一个怪物
|
||||
* @param monster
|
||||
*/
|
||||
addMonster: function (monster) {
|
||||
monster.beAddToGrid(this);
|
||||
this.map.monsters.push(monster);
|
||||
monster.start();
|
||||
},
|
||||
|
||||
/**
|
||||
* 高亮当前格子
|
||||
* @param show {Boolean}
|
||||
*/
|
||||
hightLight: function (show) {
|
||||
this.map.select_hl[show ? "show" : "hide"](this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var ctx = TD.ctx,
|
||||
px = this.x + 0.5,
|
||||
py = this.y + 0.5;
|
||||
|
||||
//if (this.map.is_main_map) {
|
||||
//ctx.drawImage(this.map.res,
|
||||
//0, 0, 32, 32, this.x, this.y, 32, 32
|
||||
//);
|
||||
//}
|
||||
|
||||
if (this.is_hover) {
|
||||
ctx.fillStyle = "rgba(255, 255, 200, 0.2)";
|
||||
ctx.beginPath();
|
||||
ctx.fillRect(px, py, this.width, this.height);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
if (this.passable_flag == 0) {
|
||||
// 不可通过
|
||||
ctx.fillStyle = "#fcc";
|
||||
ctx.beginPath();
|
||||
ctx.fillRect(px, py, this.width, this.height);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
/**
|
||||
* 画入口及出口
|
||||
*/
|
||||
if (this.is_entrance || this.is_exit) {
|
||||
ctx.lineWidth = 1;
|
||||
ctx.fillStyle = "#ccc";
|
||||
ctx.beginPath();
|
||||
ctx.fillRect(px, py, this.width, this.height);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
ctx.strokeStyle = "#666";
|
||||
ctx.fillStyle = this.is_entrance ? "#fff" : "#666";
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.cx, this.cy, TD.grid_size * 0.325, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.strokeStyle = "#eee";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.strokeRect(px, py, this.width, this.height);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
/**
|
||||
* 鼠标进入当前格子事件
|
||||
*/
|
||||
onEnter: function () {
|
||||
if (this.map.is_main_map && TD.mode == "build") {
|
||||
if (this.build_flag == 1) {
|
||||
this.map.pre_building.show();
|
||||
this.map.pre_building.locate(this);
|
||||
} else {
|
||||
this.map.pre_building.hide();
|
||||
}
|
||||
} else if (this.map.is_main_map) {
|
||||
var msg = "";
|
||||
if (this.is_entrance) {
|
||||
msg = TD._t("entrance");
|
||||
} else if (this.is_exit) {
|
||||
msg = TD._t("exit");
|
||||
} else if (this.passable_flag == 0) {
|
||||
msg = TD._t("_cant_pass");
|
||||
} else if (this.build_flag == 0) {
|
||||
msg = TD._t("_cant_build");
|
||||
}
|
||||
|
||||
if (msg) {
|
||||
this.scene.panel.balloontip.msg(msg, this);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 鼠标移出当前格子事件
|
||||
*/
|
||||
onOut: function () {
|
||||
// 如果当前气球提示指向本格子,将其隐藏
|
||||
if (this.scene.panel.balloontip.el == this) {
|
||||
this.scene.panel.balloontip.hide();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 鼠标点击了当前格子事件
|
||||
*/
|
||||
onClick: function () {
|
||||
if (this.scene.state != 1) return;
|
||||
|
||||
if (TD.mode == "build" && this.map.is_main_map && !this.building) {
|
||||
// 如果处于建设模式下,并且点击在主地图的空格子上,则尝试建设指定建筑
|
||||
if (this.checkBlock()) {
|
||||
// 起点与终点之间被阻塞,不能修建
|
||||
this.scene.panel.balloontip.msg(this._block_msg, this);
|
||||
} else {
|
||||
// 购买建筑
|
||||
this.buyBuilding(this.map.pre_building.type);
|
||||
}
|
||||
} else if (!this.building && this.map.selected_building) {
|
||||
// 取消选中建筑
|
||||
this.map.selected_building.toggleSelected();
|
||||
this.map.selected_building = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param id {String}
|
||||
* @param cfg {object} 配置对象
|
||||
* 至少需要包含以下项:
|
||||
* {
|
||||
* mx: 在 map 格子中的横向坐标,
|
||||
* my: 在 map 格子中的纵向坐标,
|
||||
* map: 属于哪个 map,
|
||||
* }
|
||||
*/
|
||||
TD.Grid = function (id, cfg) {
|
||||
cfg.on_events = ["enter", "out", "click"];
|
||||
|
||||
var grid = new TD.Element(id, cfg);
|
||||
TD.lang.mix(grid, grid_obj);
|
||||
grid._init(cfg);
|
||||
|
||||
return grid;
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
378
html5-tower-defense-master/src/js/td-obj-map.js
Normal file
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* Last Update: 2011/1/10 5:22:52
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
var _default_wait_clearInvalidElements = 20;
|
||||
|
||||
// map 对象的属性、方法。注意属性中不要有数组、对象等
|
||||
// 引用属性,否则多个实例的相关属性会发生冲突
|
||||
var map_obj = {
|
||||
_init: function (cfg) {
|
||||
cfg = cfg || {};
|
||||
this.grid_x = cfg.grid_x || 10;
|
||||
this.grid_y = cfg.grid_y || 10;
|
||||
this.x = cfg.x || 0;
|
||||
this.y = cfg.y || 0;
|
||||
this.width = this.grid_x * TD.grid_size;
|
||||
this.height = this.grid_y * TD.grid_size;
|
||||
this.x2 = this.x + this.width;
|
||||
this.y2 = this.y + this.height;
|
||||
this.grids = [];
|
||||
this.entrance = this.exit = null;
|
||||
this.buildings = [];
|
||||
this.monsters = [];
|
||||
this.bullets = [];
|
||||
this.scene = cfg.scene;
|
||||
this.is_main_map = !!cfg.is_main_map;
|
||||
this.select_hl = TD.MapSelectHighLight(this.id + "-hl", {
|
||||
map: this
|
||||
});
|
||||
this.select_hl.addToScene(this.scene, 1, 9);
|
||||
this.selected_building = null;
|
||||
this._wait_clearInvalidElements = _default_wait_clearInvalidElements;
|
||||
this._wait_add_monsters = 0;
|
||||
this._wait_add_monsters_arr = [];
|
||||
if (this.is_main_map) {
|
||||
this.mmm = new MainMapMask(this.id + "-mmm", {
|
||||
map: this
|
||||
});
|
||||
this.mmm.addToScene(this.scene, 1, 7);
|
||||
|
||||
}
|
||||
|
||||
// 下面添加相应的格子
|
||||
var i, l = this.grid_x * this.grid_y,
|
||||
grid_data = cfg["grid_data"] || [],
|
||||
d, grid;
|
||||
|
||||
for (i = 0; i < l; i++) {
|
||||
d = grid_data[i] || {};
|
||||
d.mx = i % this.grid_x;
|
||||
d.my = Math.floor(i / this.grid_x);
|
||||
d.map = this;
|
||||
d.step_level = this.step_level;
|
||||
d.render_level = this.render_level;
|
||||
grid = new TD.Grid(this.id + "-grid-" + d.mx + "-" + d.my, d);
|
||||
this.grids.push(grid);
|
||||
}
|
||||
|
||||
if (cfg.entrance && cfg.exit && !TD.lang.arrayEqual(cfg.entrance, cfg.exit)) {
|
||||
this.entrance = this.getGrid(cfg.entrance[0], cfg.entrance[1]);
|
||||
this.entrance.is_entrance = true;
|
||||
this.exit = this.getGrid(cfg.exit[0], cfg.exit[1]);
|
||||
this.exit.is_exit = true;
|
||||
}
|
||||
|
||||
var _this = this;
|
||||
if (cfg.grids_cfg) {
|
||||
TD.lang.each(cfg.grids_cfg, function (obj) {
|
||||
var grid = _this.getGrid(obj.pos[0], obj.pos[1]);
|
||||
if (!grid) return;
|
||||
if (!isNaN(obj.passable_flag))
|
||||
grid.passable_flag = obj.passable_flag;
|
||||
if (!isNaN(obj.build_flag))
|
||||
grid.build_flag = obj.build_flag;
|
||||
if (obj.building) {
|
||||
grid.addBuilding(obj.building);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查地图中是否有武器(具备攻击性的建筑)
|
||||
* 因为第一波怪物只有在地图上有了第一件武器后才会出现
|
||||
*/
|
||||
checkHasWeapon: function () {
|
||||
this.has_weapon = (this.anyBuilding(function (obj) {
|
||||
return obj.is_weapon;
|
||||
}) != null);
|
||||
},
|
||||
|
||||
/**
|
||||
* 取得指定位置的格子对象
|
||||
* @param mx {Number} 地图上的坐标 x
|
||||
* @param my {Number} 地图上的坐标 y
|
||||
*/
|
||||
getGrid: function (mx, my) {
|
||||
var p = my * this.grid_x + mx;
|
||||
return this.grids[p];
|
||||
},
|
||||
|
||||
anyMonster: function (f) {
|
||||
return TD.lang.any(this.monsters, f);
|
||||
},
|
||||
anyBuilding: function (f) {
|
||||
return TD.lang.any(this.buildings, f);
|
||||
},
|
||||
anyBullet: function (f) {
|
||||
return TD.lang.any(this.bullets, f);
|
||||
},
|
||||
eachBuilding: function (f) {
|
||||
TD.lang.each(this.buildings, f);
|
||||
},
|
||||
eachMonster: function (f) {
|
||||
TD.lang.each(this.monsters, f);
|
||||
},
|
||||
eachBullet: function (f) {
|
||||
TD.lang.each(this.bullets, f);
|
||||
},
|
||||
|
||||
/**
|
||||
* 预建设
|
||||
* @param building_type {String}
|
||||
*/
|
||||
preBuild: function (building_type) {
|
||||
TD.mode = "build";
|
||||
if (this.pre_building) {
|
||||
this.pre_building.remove();
|
||||
}
|
||||
|
||||
this.pre_building = new TD.Building(this.id + "-" + "pre-building-" + TD.lang.rndStr(), {
|
||||
type: building_type,
|
||||
map: this,
|
||||
is_pre_building: true
|
||||
});
|
||||
this.scene.addElement(this.pre_building, 1, this.render_level + 1);
|
||||
//this.show_all_ranges = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* 退出预建设状态
|
||||
*/
|
||||
cancelPreBuild: function () {
|
||||
TD.mode = "normal";
|
||||
if (this.pre_building) {
|
||||
this.pre_building.remove();
|
||||
}
|
||||
//this.show_all_ranges = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除地图上无效的元素
|
||||
*/
|
||||
clearInvalidElements: function () {
|
||||
if (this._wait_clearInvalidElements > 0) {
|
||||
this._wait_clearInvalidElements--;
|
||||
return;
|
||||
}
|
||||
this._wait_clearInvalidElements = _default_wait_clearInvalidElements;
|
||||
|
||||
var a = [];
|
||||
TD.lang.shift(this.buildings, function (obj) {
|
||||
if (obj.is_valid)
|
||||
a.push(obj);
|
||||
});
|
||||
this.buildings = a;
|
||||
|
||||
a = [];
|
||||
TD.lang.shift(this.monsters, function (obj) {
|
||||
if (obj.is_valid)
|
||||
a.push(obj);
|
||||
});
|
||||
this.monsters = a;
|
||||
|
||||
a = [];
|
||||
TD.lang.shift(this.bullets, function (obj) {
|
||||
if (obj.is_valid)
|
||||
a.push(obj);
|
||||
});
|
||||
this.bullets = a;
|
||||
},
|
||||
|
||||
/**
|
||||
* 在地图的入口处添加一个怪物
|
||||
* @param monster 可以是数字,也可以是 monster 对象
|
||||
*/
|
||||
addMonster: function (monster) {
|
||||
if (!this.entrance) return;
|
||||
if (typeof monster == "number") {
|
||||
monster = new TD.Monster(null, {
|
||||
idx: monster,
|
||||
difficulty: TD.difficulty,
|
||||
step_level: this.step_level,
|
||||
render_level: this.render_level + 2
|
||||
});
|
||||
}
|
||||
this.entrance.addMonster(monster);
|
||||
},
|
||||
|
||||
/**
|
||||
* 在地图的入口处添加 n 个怪物
|
||||
* @param n
|
||||
* @param monster
|
||||
*/
|
||||
addMonsters: function (n, monster) {
|
||||
this._wait_add_monsters = n;
|
||||
this._wait_add_monsters_objidx = monster;
|
||||
},
|
||||
|
||||
/**
|
||||
* arr 的格式形如:
|
||||
* [[1, 0], [2, 5], [3, 6], [10, 4]...]
|
||||
*/
|
||||
addMonsters2: function (arr) {
|
||||
this._wait_add_monsters_arr = arr;
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查地图的指定格子是否可通过
|
||||
* @param mx {Number}
|
||||
* @param my {Number}
|
||||
*/
|
||||
checkPassable: function (mx, my) {
|
||||
var grid = this.getGrid(mx, my);
|
||||
return (grid != null && grid.passable_flag == 1 && grid.build_flag != 2);
|
||||
},
|
||||
|
||||
step: function () {
|
||||
this.clearInvalidElements();
|
||||
|
||||
if (this._wait_add_monsters > 0) {
|
||||
this.addMonster(this._wait_add_monsters_objidx);
|
||||
this._wait_add_monsters--;
|
||||
} else if (this._wait_add_monsters_arr.length > 0) {
|
||||
var a = this._wait_add_monsters_arr.shift();
|
||||
this.addMonsters(a[0], a[1]);
|
||||
}
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var ctx = TD.ctx;
|
||||
ctx.strokeStyle = "#99a";
|
||||
ctx.lineWidth = _TD.retina;
|
||||
ctx.beginPath();
|
||||
ctx.strokeRect(this.x + 0.5, this.y + 0.5, this.width, this.height);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
/**
|
||||
* 鼠标移出地图事件
|
||||
*/
|
||||
onOut: function () {
|
||||
if (this.is_main_map && this.pre_building)
|
||||
this.pre_building.hide();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param id {String} 配置对象
|
||||
* @param cfg {Object} 配置对象
|
||||
* 至少需要包含以下项:
|
||||
* {
|
||||
* grid_x: 宽度(格子),
|
||||
* grid_y: 高度(格子),
|
||||
* scene: 属于哪个场景,
|
||||
* }
|
||||
*/
|
||||
TD.Map = function (id, cfg) {
|
||||
// map 目前只需要监听 out 事件
|
||||
// 虽然只需要监听 out 事件,但同时也需要监听 enter ,因为如果
|
||||
// 没有 enter ,out 将永远不会被触发
|
||||
cfg.on_events = ["enter", "out"];
|
||||
var map = new TD.Element(id, cfg);
|
||||
TD.lang.mix(map, map_obj);
|
||||
map._init(cfg);
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 地图选中元素高亮边框对象
|
||||
*/
|
||||
var map_selecthl_obj = {
|
||||
_init: function (cfg) {
|
||||
this.map = cfg.map;
|
||||
this.width = TD.grid_size + 2;
|
||||
this.height = TD.grid_size + 2;
|
||||
this.is_visiable = false;
|
||||
},
|
||||
show: function (grid) {
|
||||
this.x = grid.x;
|
||||
this.y = grid.y;
|
||||
this.is_visiable = true;
|
||||
},
|
||||
render: function () {
|
||||
var ctx = TD.ctx;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = "#f93";
|
||||
ctx.beginPath();
|
||||
ctx.strokeRect(this.x, this.y, this.width - 1, this.height - 1);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 地图选中的高亮框
|
||||
* @param id {String} 至少需要包含
|
||||
* @param cfg {Object} 至少需要包含
|
||||
* {
|
||||
* map: map 对象
|
||||
* }
|
||||
*/
|
||||
TD.MapSelectHighLight = function (id, cfg) {
|
||||
var map_selecthl = new TD.Element(id, cfg);
|
||||
TD.lang.mix(map_selecthl, map_selecthl_obj);
|
||||
map_selecthl._init(cfg);
|
||||
|
||||
return map_selecthl;
|
||||
};
|
||||
|
||||
|
||||
var mmm_obj = {
|
||||
_init: function (cfg) {
|
||||
this.map = cfg.map;
|
||||
|
||||
this.x1 = this.map.x;
|
||||
this.y1 = this.map.y;
|
||||
this.x2 = this.map.x2 + 1;
|
||||
this.y2 = this.map.y2 + 1;
|
||||
this.w = this.map.scene.stage.width;
|
||||
this.h = this.map.scene.stage.height;
|
||||
this.w2 = this.w - this.x2;
|
||||
this.h2 = this.h - this.y2;
|
||||
},
|
||||
render: function () {
|
||||
var ctx = TD.ctx;
|
||||
/*ctx.clearRect(0, 0, this.x1, this.h);
|
||||
ctx.clearRect(0, 0, this.w, this.y1);
|
||||
ctx.clearRect(0, this.y2, this.w, this.h2);
|
||||
ctx.clearRect(this.x2, 0, this.w2, this.h2);*/
|
||||
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.beginPath();
|
||||
ctx.fillRect(0, 0, this.x1, this.h);
|
||||
ctx.fillRect(0, 0, this.w, this.y1);
|
||||
ctx.fillRect(0, this.y2, this.w, this.h2);
|
||||
ctx.fillRect(this.x2, 0, this.w2, this.h);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 主地图外边的遮罩,用于遮住超出地图的射程等
|
||||
*/
|
||||
function MainMapMask(id, cfg) {
|
||||
var mmm = new TD.Element(id, cfg);
|
||||
TD.lang.mix(mmm, mmm_obj);
|
||||
mmm._init(cfg);
|
||||
|
||||
return mmm;
|
||||
}
|
||||
|
||||
}); // _TD.a.push end
|
||||
|
||||
393
html5-tower-defense-master/src/js/td-obj-monster.js
Normal file
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* Last Update: 2011/1/10 5:22:52
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
// monster 对象的属性、方法。注意属性中不要有数组、对象等
|
||||
// 引用属性,否则多个实例的相关属性会发生冲突
|
||||
var monster_obj = {
|
||||
_init: function (cfg) {
|
||||
cfg = cfg || {};
|
||||
this.is_monster = true;
|
||||
this.idx = cfg.idx || 1;
|
||||
this.difficulty = cfg.difficulty || 1.0;
|
||||
var attr = TD.getDefaultMonsterAttributes(this.idx);
|
||||
|
||||
this.speed = Math.floor(
|
||||
(attr.speed + this.difficulty / 2) * (Math.random() * 0.5 + 0.75)
|
||||
);
|
||||
if (this.speed < 1) this.speed = 1;
|
||||
if (this.speed > cfg.max_speed) this.speed = cfg.max_speed;
|
||||
|
||||
this.life = this.life0 = Math.floor(
|
||||
attr.life * (this.difficulty + 1) * (Math.random() + 0.5) * 0.5
|
||||
);
|
||||
if (this.life < 1) this.life = this.life0 = 1;
|
||||
|
||||
this.shield = Math.floor(attr.shield + this.difficulty / 2);
|
||||
if (this.shield < 0) this.shield = 0;
|
||||
|
||||
this.damage = Math.floor(
|
||||
(attr.damage || 1) * (Math.random() * 0.5 + 0.75)
|
||||
);
|
||||
if (this.damage < 1) this.damage = 1;
|
||||
|
||||
this.money = attr.money || Math.floor(
|
||||
Math.sqrt((this.speed + this.life) * (this.shield + 1) * this.damage)
|
||||
);
|
||||
if (this.money < 1) this.money = 1;
|
||||
|
||||
this.color = attr.color || TD.lang.rndRGB();
|
||||
this.r = Math.floor(this.damage * 1.2) * _TD.retina;
|
||||
if (this.r < (4 * _TD.retina)) this.r = 4 * _TD.retina;
|
||||
if (this.r > TD.grid_size / 2 - (4 * _TD.retina)) this.r = TD.grid_size / 2 - (4 * _TD.retina);
|
||||
this.render = attr.render;
|
||||
|
||||
this.grid = null; // 当前格子
|
||||
this.map = null;
|
||||
this.next_grid = null;
|
||||
this.way = [];
|
||||
this.toward = 2; // 默认面朝下方
|
||||
this._dx = 0;
|
||||
this._dy = 0;
|
||||
|
||||
this.is_blocked = false; // 前进的道路是否被阻塞了
|
||||
},
|
||||
caculatePos: function () {
|
||||
// if (!this.map) return;
|
||||
var r = this.r;
|
||||
this.x = this.cx - r;
|
||||
this.y = this.cy - r;
|
||||
this.x2 = this.cx + r;
|
||||
this.y2 = this.cy + r;
|
||||
},
|
||||
|
||||
/**
|
||||
* 怪物被击中
|
||||
* @param building {Element} 对应的建筑(武器)
|
||||
* @param damage {Number} 本次攻击的原始伤害值
|
||||
*/
|
||||
beHit: function (building, damage) {
|
||||
if (!this.is_valid) return;
|
||||
var min_damage = Math.ceil(damage * 0.1);
|
||||
damage -= this.shield;
|
||||
if (damage <= min_damage) damage = min_damage;
|
||||
|
||||
this.life -= damage;
|
||||
TD.score += Math.floor(Math.sqrt(damage));
|
||||
if (this.life <= 0) {
|
||||
this.beKilled(building);
|
||||
}
|
||||
|
||||
var balloontip = this.scene.panel.balloontip;
|
||||
if (balloontip.el == this) {
|
||||
balloontip.text = TD._t("monster_info", [this.life, this.shield, this.speed, this.damage]);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 怪物被杀死
|
||||
* @param building {Element} 对应的建筑(武器)
|
||||
*/
|
||||
beKilled: function (building) {
|
||||
if (!this.is_valid) return;
|
||||
this.life = 0;
|
||||
this.is_valid = false;
|
||||
|
||||
TD.money += this.money;
|
||||
building.killed++;
|
||||
|
||||
TD.Explode(this.id + "-explode", {
|
||||
cx: this.cx,
|
||||
cy: this.cy,
|
||||
color: this.color,
|
||||
r: this.r,
|
||||
step_level: this.step_level,
|
||||
render_level: this.render_level,
|
||||
scene: this.grid.scene
|
||||
});
|
||||
},
|
||||
arrive: function () {
|
||||
this.grid = this.next_grid;
|
||||
this.next_grid = null;
|
||||
this.checkFinish();
|
||||
},
|
||||
findWay: function () {
|
||||
var _this = this;
|
||||
var fw = new TD.FindWay(
|
||||
this.map.grid_x, this.map.grid_y,
|
||||
this.grid.mx, this.grid.my,
|
||||
this.map.exit.mx, this.map.exit.my,
|
||||
function (x, y) {
|
||||
return _this.map.checkPassable(x, y);
|
||||
}
|
||||
);
|
||||
this.way = fw.way;
|
||||
//delete fw;
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查是否已到达终点
|
||||
*/
|
||||
checkFinish: function () {
|
||||
if (this.grid && this.map && this.grid == this.map.exit) {
|
||||
TD.life -= this.damage;
|
||||
TD.wave_damage += this.damage;
|
||||
if (TD.life <= 0) {
|
||||
TD.life = 0;
|
||||
TD.stage.gameover();
|
||||
} else {
|
||||
this.pause();
|
||||
this.del();
|
||||
}
|
||||
}
|
||||
},
|
||||
beAddToGrid: function (grid) {
|
||||
this.grid = grid;
|
||||
this.map = grid.map;
|
||||
this.cx = grid.cx;
|
||||
this.cy = grid.cy;
|
||||
|
||||
this.grid.scene.addElement(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* 取得朝向
|
||||
* 即下一个格子在当前格子的哪边
|
||||
* 0:上;1:右;2:下;3:左
|
||||
*/
|
||||
getToward: function () {
|
||||
if (!this.grid || !this.next_grid) return;
|
||||
if (this.grid.my < this.next_grid.my) {
|
||||
this.toward = 0;
|
||||
} else if (this.grid.mx < this.next_grid.mx) {
|
||||
this.toward = 1;
|
||||
} else if (this.grid.my > this.next_grid.my) {
|
||||
this.toward = 2;
|
||||
} else if (this.grid.mx > this.next_grid.mx) {
|
||||
this.toward = 3;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 取得要去的下一个格子
|
||||
*/
|
||||
getNextGrid: function () {
|
||||
if (this.way.length == 0 ||
|
||||
Math.random() < 0.1 // 有 1/10 的概率自动重新寻路
|
||||
) {
|
||||
this.findWay();
|
||||
}
|
||||
|
||||
var next_grid = this.way.shift();
|
||||
if (next_grid && !this.map.checkPassable(next_grid[0], next_grid[1])) {
|
||||
this.findWay();
|
||||
next_grid = this.way.shift();
|
||||
}
|
||||
|
||||
if (!next_grid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.next_grid = this.map.getGrid(next_grid[0], next_grid[1]);
|
||||
// this.getToward(); // 在这个版本中暂时没有用
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查假如在地图 (x, y) 的位置修建建筑,是否会阻塞当前怪物
|
||||
* @param mx {Number} 地图的 x 坐标
|
||||
* @param my {Number} 地图的 y 坐标
|
||||
* @return {Boolean}
|
||||
*/
|
||||
chkIfBlocked: function (mx, my) {
|
||||
|
||||
var _this = this,
|
||||
fw = new TD.FindWay(
|
||||
this.map.grid_x, this.map.grid_y,
|
||||
this.grid.mx, this.grid.my,
|
||||
this.map.exit.mx, this.map.exit.my,
|
||||
function (x, y) {
|
||||
return !(x == mx && y == my) &&
|
||||
_this.map.checkPassable(x, y);
|
||||
}
|
||||
);
|
||||
|
||||
return fw.is_blocked;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 怪物前进的道路被阻塞(被建筑包围了)
|
||||
*/
|
||||
beBlocked: function () {
|
||||
if (this.is_blocked) return;
|
||||
|
||||
this.is_blocked = true;
|
||||
TD.log("monster be blocked!");
|
||||
},
|
||||
|
||||
step: function () {
|
||||
if (!this.is_valid || this.is_paused || !this.grid) return;
|
||||
|
||||
if (!this.next_grid) {
|
||||
this.getNextGrid();
|
||||
|
||||
/**
|
||||
* 如果依旧找不着下一步可去的格子,说明当前怪物被阻塞了
|
||||
*/
|
||||
if (!this.next_grid) {
|
||||
this.beBlocked();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.cx == this.next_grid.cx && this.cy == this.next_grid.cy) {
|
||||
this.arrive();
|
||||
} else {
|
||||
// 移动到 next grid
|
||||
|
||||
var dpx = this.next_grid.cx - this.cx,
|
||||
dpy = this.next_grid.cy - this.cy,
|
||||
sx = dpx < 0 ? -1 : 1,
|
||||
sy = dpy < 0 ? -1 : 1,
|
||||
speed = this.speed * TD.global_speed;
|
||||
|
||||
if (Math.abs(dpx) < speed && Math.abs(dpy) < speed) {
|
||||
this.cx = this.next_grid.cx;
|
||||
this.cy = this.next_grid.cy;
|
||||
this._dx = speed - Math.abs(dpx);
|
||||
this._dy = speed - Math.abs(dpy);
|
||||
} else {
|
||||
this.cx += dpx == 0 ? 0 : sx * (speed + this._dx);
|
||||
this.cy += dpy == 0 ? 0 : sy * (speed + this._dy);
|
||||
this._dx = 0;
|
||||
this._dy = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this.caculatePos();
|
||||
},
|
||||
|
||||
onEnter: function () {
|
||||
var msg,
|
||||
balloontip = this.scene.panel.balloontip;
|
||||
|
||||
if (balloontip.el == this) {
|
||||
balloontip.hide();
|
||||
balloontip.el = null;
|
||||
} else {
|
||||
msg = TD._t("monster_info",
|
||||
[this.life, this.shield, this.speed, this.damage]);
|
||||
balloontip.msg(msg, this);
|
||||
}
|
||||
},
|
||||
|
||||
onOut: function () {
|
||||
// if (this.scene.panel.balloontip.el == this) {
|
||||
// this.scene.panel.balloontip.hide();
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param id {String}
|
||||
* @param cfg {Object} 配置对象
|
||||
* 至少需要包含以下项:
|
||||
* {
|
||||
* life: 怪物的生命值
|
||||
* shield: 怪物的防御值
|
||||
* speed: 怪物的速度
|
||||
* }
|
||||
*/
|
||||
TD.Monster = function (id, cfg) {
|
||||
cfg.on_events = ["enter", "out"];
|
||||
var monster = new TD.Element(id, cfg);
|
||||
TD.lang.mix(monster, monster_obj);
|
||||
monster._init(cfg);
|
||||
|
||||
return monster;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 怪物死亡时的爆炸效果对象
|
||||
*/
|
||||
var explode_obj = {
|
||||
_init: function (cfg) {
|
||||
cfg = cfg || {};
|
||||
|
||||
var rgb = TD.lang.rgb2Arr(cfg.color);
|
||||
this.cx = cfg.cx;
|
||||
this.cy = cfg.cy;
|
||||
this.r = cfg.r * _TD.retina;
|
||||
this.step_level = cfg.step_level;
|
||||
this.render_level = cfg.render_level;
|
||||
|
||||
this.rgb_r = rgb[0];
|
||||
this.rgb_g = rgb[1];
|
||||
this.rgb_b = rgb[2];
|
||||
this.rgb_a = 1;
|
||||
|
||||
this.wait = this.wait0 = TD.exp_fps * (cfg.time || 1);
|
||||
|
||||
cfg.scene.addElement(this);
|
||||
},
|
||||
step: function () {
|
||||
if (!this.is_valid) return;
|
||||
|
||||
this.wait--;
|
||||
this.r++;
|
||||
|
||||
this.is_valid = this.wait > 0;
|
||||
this.rgb_a = this.wait / this.wait0;
|
||||
},
|
||||
render: function () {
|
||||
var ctx = TD.ctx;
|
||||
|
||||
ctx.fillStyle = "rgba(" + this.rgb_r + "," + this.rgb_g + ","
|
||||
+ this.rgb_b + "," + this.rgb_a + ")";
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.cx, this.cy, this.r, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param id {String}
|
||||
* @param cfg {Object} 配置对象
|
||||
* {
|
||||
* // 至少需要包含以下项:
|
||||
* cx: 中心 x 坐标
|
||||
* cy: 中心 y 坐标
|
||||
* r: 半径
|
||||
* color: RGB色彩,形如“#f98723”
|
||||
* scene: Scene 对象
|
||||
* step_level:
|
||||
* render_level:
|
||||
*
|
||||
* // 以下项可选:
|
||||
* time: 持续时间,默认为 1,单位大致为秒(根据渲染情况而定,不是很精确)
|
||||
* }
|
||||
*/
|
||||
TD.Explode = function (id, cfg) {
|
||||
// cfg.on_events = ["enter", "out"];
|
||||
var explode = new TD.Element(id, cfg);
|
||||
TD.lang.mix(explode, explode_obj);
|
||||
explode._init(cfg);
|
||||
|
||||
return explode;
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
|
||||
|
||||
575
html5-tower-defense-master/src/js/td-obj-panel.js
Normal file
@@ -0,0 +1,575 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* Last Update: 2011/1/10 5:22:52
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
// panel 对象的属性、方法。注意属性中不要有数组、对象等
|
||||
// 引用属性,否则多个实例的相关属性会发生冲突
|
||||
var panel_obj = {
|
||||
_init: function (cfg) {
|
||||
cfg = cfg || {};
|
||||
this.x = cfg.x;
|
||||
this.y = cfg.y;
|
||||
this.scene = cfg.scene;
|
||||
this.map = cfg.main_map;
|
||||
|
||||
// make panel map
|
||||
var panel_map = new TD.Map("panel-map", TD.lang.mix({
|
||||
x: this.x + cfg.map.x,
|
||||
y: this.y + cfg.map.y,
|
||||
scene: this.scene,
|
||||
step_level: this.step_level,
|
||||
render_level: this.render_level
|
||||
}, cfg.map, false));
|
||||
|
||||
this.addToScene(this.scene, 1, 7);
|
||||
panel_map.addToScene(this.scene, 1, 7, panel_map.grids);
|
||||
this.scene.panel_map = panel_map;
|
||||
this.gameover_obj = new TD.GameOver("panel-gameover", {
|
||||
panel: this,
|
||||
scene: this.scene,
|
||||
step_level: this.step_level,
|
||||
is_visiable: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.scene.stage.width,
|
||||
height: this.scene.stage.height,
|
||||
render_level: 9
|
||||
});
|
||||
|
||||
this.balloontip = new TD.BalloonTip("panel-balloon-tip", {
|
||||
scene: this.scene,
|
||||
step_level: this.step_level,
|
||||
render_level: 9
|
||||
});
|
||||
this.balloontip.addToScene(this.scene, 1, 9);
|
||||
|
||||
// make buttons
|
||||
// 暂停按钮
|
||||
this.btn_pause = new TD.Button("panel-btn-pause", {
|
||||
scene: this.scene,
|
||||
x: this.x,
|
||||
y: this.y + 260 * _TD.retina,
|
||||
text: TD._t("button_pause_text"),
|
||||
//desc: TD._t("button_pause_desc_0"),
|
||||
step_level: this.step_level,
|
||||
render_level: this.render_level + 1,
|
||||
onClick: function () {
|
||||
if (this.scene.state == 1) {
|
||||
this.scene.pause();
|
||||
this.text = TD._t("button_continue_text");
|
||||
this.scene.panel.btn_upgrade.hide();
|
||||
this.scene.panel.btn_sell.hide();
|
||||
this.scene.panel.btn_restart.show();
|
||||
//this.desc = TD._t("button_pause_desc_1");
|
||||
} else if (this.scene.state == 2) {
|
||||
this.scene.start();
|
||||
this.text = TD._t("button_pause_text");
|
||||
this.scene.panel.btn_restart.hide();
|
||||
if (this.scene.map.selected_building) {
|
||||
this.scene.panel.btn_upgrade.show();
|
||||
this.scene.panel.btn_sell.show();
|
||||
}
|
||||
//this.desc = TD._t("button_pause_desc_0");
|
||||
}
|
||||
}
|
||||
});
|
||||
// 重新开始按钮
|
||||
this.btn_restart = new TD.Button("panel-btn-restart", {
|
||||
scene: this.scene,
|
||||
x: this.x,
|
||||
y: this.y + 300 * _TD.retina,
|
||||
is_visiable: false,
|
||||
text: TD._t("button_restart_text"),
|
||||
step_level: this.step_level,
|
||||
render_level: this.render_level + 1,
|
||||
onClick: function () {
|
||||
setTimeout(function () {
|
||||
TD.stage.clear();
|
||||
TD.is_paused = true;
|
||||
TD.start();
|
||||
TD.mouseHand(false);
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
// 建筑升级按钮
|
||||
this.btn_upgrade = new TD.Button("panel-btn-upgrade", {
|
||||
scene: this.scene,
|
||||
x: this.x,
|
||||
y: this.y + 300 * _TD.retina,
|
||||
is_visiable: false,
|
||||
text: TD._t("button_upgrade_text"),
|
||||
step_level: this.step_level,
|
||||
render_level: this.render_level + 1,
|
||||
onClick: function () {
|
||||
this.scene.map.selected_building.tryToUpgrade(this);
|
||||
}
|
||||
});
|
||||
// 建筑出售按钮
|
||||
this.btn_sell = new TD.Button("panel-btn-sell", {
|
||||
scene: this.scene,
|
||||
x: this.x,
|
||||
y: this.y + 340 * _TD.retina,
|
||||
is_visiable: false,
|
||||
text: TD._t("button_sell_text"),
|
||||
step_level: this.step_level,
|
||||
render_level: this.render_level + 1,
|
||||
onClick: function () {
|
||||
this.scene.map.selected_building.tryToSell(this);
|
||||
}
|
||||
});
|
||||
|
||||
// 速度控制滑动条
|
||||
this.speed_slider = new TD.Slider("panel-speed-slider", {
|
||||
scene: this.scene,
|
||||
x: this.x,
|
||||
y: this.y + 400 * _TD.retina,
|
||||
width: 80 * _TD.retina,
|
||||
height: 10 * _TD.retina,
|
||||
min: 0,
|
||||
max: 4,
|
||||
value: 0, // 默认 1x (2^0)
|
||||
step_level: this.step_level,
|
||||
render_level: this.render_level + 1,
|
||||
onChange: function (value) {
|
||||
// value: 0->1x, 1->2x, 2->4x, 3->8x, 4->16x
|
||||
var multiplier = Math.pow(2, value); // 2^value
|
||||
TD.global_speed = 0.1 * multiplier;
|
||||
}
|
||||
});
|
||||
},
|
||||
step: function () {
|
||||
if (TD.life_recover) {
|
||||
this._life_recover = this._life_recover2 = TD.life_recover;
|
||||
this._life_recover_wait = this._life_recover_wait2 = TD.exp_fps * 3;
|
||||
TD.life_recover = 0;
|
||||
}
|
||||
|
||||
if (this._life_recover && (TD.iframe % TD.exp_fps_eighth == 0)) {
|
||||
TD.life ++;
|
||||
this._life_recover --;
|
||||
}
|
||||
|
||||
},
|
||||
render: function () {
|
||||
// 画状态文字
|
||||
var ctx = TD.ctx;
|
||||
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.font = "normal " + (12 * _TD.retina) + "px 'Courier New'";
|
||||
ctx.beginPath();
|
||||
ctx.fillText(TD._t("panel_money_title") + TD.money, this.x, this.y);
|
||||
ctx.fillText(TD._t("panel_score_title") + TD.score, this.x, this.y + 20 * _TD.retina);
|
||||
ctx.fillText(TD._t("panel_life_title") + TD.life, this.x, this.y + 40 * _TD.retina);
|
||||
ctx.fillText(TD._t("panel_building_title") + this.map.buildings.length,
|
||||
this.x, this.y + 60 * _TD.retina);
|
||||
ctx.fillText(TD._t("panel_monster_title") + this.map.monsters.length,
|
||||
this.x, this.y + 80 * _TD.retina);
|
||||
ctx.fillText(TD._t("wave_info", [this.scene.wave]), this.x, this.y + 210 * _TD.retina);
|
||||
ctx.closePath();
|
||||
|
||||
if (this._life_recover_wait) {
|
||||
// 画生命恢复提示
|
||||
var a = this._life_recover_wait / this._life_recover_wait2;
|
||||
ctx.fillStyle = "rgba(255, 0, 0, " + a + ")";
|
||||
ctx.font = "bold " + (12 * _TD.retina) + "px 'Verdana'";
|
||||
ctx.beginPath();
|
||||
ctx.fillText("+" + this._life_recover2, this.x + 60 * _TD.retina, this.y + 40 * _TD.retina);
|
||||
ctx.closePath();
|
||||
this._life_recover_wait --;
|
||||
}
|
||||
|
||||
// 在右下角画版本信息
|
||||
ctx.textAlign = "right";
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.font = "normal " + (12 * _TD.retina) + "px 'Courier New'";
|
||||
ctx.beginPath();
|
||||
ctx.fillText("version: " + TD.version + " | oldj.net", TD.stage.width - TD.padding,
|
||||
TD.stage.height - TD.padding * 2);
|
||||
ctx.closePath();
|
||||
|
||||
// 在左下角画FPS信息
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.font = "normal " + (12 * _TD.retina) + "px 'Courier New'";
|
||||
ctx.beginPath();
|
||||
ctx.fillText("FPS: " + TD.fps, TD.padding, TD.stage.height - TD.padding * 2);
|
||||
ctx.closePath();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param id {String}
|
||||
* @param cfg {Object} 配置对象
|
||||
* 至少需要包含以下项:
|
||||
* {
|
||||
* life: 怪物的生命值
|
||||
* shield: 怪物的防御值
|
||||
* speed: 怪物的速度
|
||||
* }
|
||||
*/
|
||||
TD.Panel = function (id, cfg) {
|
||||
var panel = new TD.Element(id, cfg);
|
||||
TD.lang.mix(panel, panel_obj);
|
||||
panel._init(cfg);
|
||||
|
||||
return panel;
|
||||
};
|
||||
|
||||
// balloon tip对象的属性、方法。注意属性中不要有数组、对象等
|
||||
// 引用属性,否则多个实例的相关属性会发生冲突
|
||||
var balloontip_obj = {
|
||||
_init: function (cfg) {
|
||||
cfg = cfg || {};
|
||||
this.scene = cfg.scene;
|
||||
},
|
||||
caculatePos: function () {
|
||||
var el = this.el;
|
||||
|
||||
this.x = el.cx + 0.5;
|
||||
this.y = el.cy + 0.5;
|
||||
|
||||
if (this.x + this.width > this.scene.stage.width - TD.padding) {
|
||||
this.x = this.x - this.width;
|
||||
}
|
||||
|
||||
this.px = this.x + 5 * _TD.retina;
|
||||
this.py = this.y + 4 * _TD.retina;
|
||||
},
|
||||
msg: function (txt, el) {
|
||||
this.text = txt;
|
||||
var ctx = TD.ctx;
|
||||
ctx.font = "normal " + (12 * _TD.retina) + "px 'Courier New'";
|
||||
this.width = Math.max(
|
||||
ctx.measureText(txt).width + 10 * _TD.retina,
|
||||
TD.lang.strLen2(txt) * 6 + 10 * _TD.retina
|
||||
);
|
||||
this.height = 20 * _TD.retina;
|
||||
|
||||
if (el && el.cx && el.cy) {
|
||||
this.el = el;
|
||||
this.caculatePos();
|
||||
|
||||
this.show();
|
||||
}
|
||||
},
|
||||
step: function () {
|
||||
if (!this.el || !this.el.is_valid) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.el.is_monster) {
|
||||
// monster 会移动,所以需要重新计算 tip 的位置
|
||||
this.caculatePos();
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
if (!this.el) return;
|
||||
var ctx = TD.ctx;
|
||||
|
||||
ctx.lineWidth = _TD.retina;
|
||||
ctx.fillStyle = "rgba(255, 255, 0, 0.5)";
|
||||
ctx.strokeStyle = "rgba(222, 222, 0, 0.9)";
|
||||
ctx.beginPath();
|
||||
ctx.rect(this.x, this.y, this.width, this.height);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.font = "normal " + (12 * _TD.retina) + "px 'Courier New'";
|
||||
ctx.beginPath();
|
||||
ctx.fillText(this.text, this.px, this.py);
|
||||
ctx.closePath();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param id {String}
|
||||
* @param cfg {Object} 配置对象
|
||||
* 至少需要包含以下项:
|
||||
* {
|
||||
* scene: scene
|
||||
* }
|
||||
*/
|
||||
TD.BalloonTip = function (id, cfg) {
|
||||
var balloontip = new TD.Element(id, cfg);
|
||||
TD.lang.mix(balloontip, balloontip_obj);
|
||||
balloontip._init(cfg);
|
||||
|
||||
return balloontip;
|
||||
};
|
||||
|
||||
// button 对象的属性、方法。注意属性中不要有数组、对象等
|
||||
// 引用属性,否则多个实例的相关属性会发生冲突
|
||||
var button_obj = {
|
||||
_init: function (cfg) {
|
||||
cfg = cfg || {};
|
||||
this.text = cfg.text;
|
||||
this.onClick = cfg.onClick || TD.lang.nullFunc;
|
||||
this.x = cfg.x;
|
||||
this.y = cfg.y;
|
||||
this.width = cfg.width || 80 * _TD.retina;
|
||||
this.height = cfg.height || 30 * _TD.retina;
|
||||
this.font_x = this.x + 8 * _TD.retina;
|
||||
this.font_y = this.y + 9 * _TD.retina;
|
||||
this.scene = cfg.scene;
|
||||
this.desc = cfg.desc || "";
|
||||
|
||||
this.addToScene(this.scene, this.step_level, this.render_level);
|
||||
this.caculatePos();
|
||||
},
|
||||
onEnter: function () {
|
||||
TD.mouseHand(true);
|
||||
if (this.desc) {
|
||||
this.scene.panel.balloontip.msg(this.desc, this);
|
||||
}
|
||||
},
|
||||
onOut: function () {
|
||||
TD.mouseHand(false);
|
||||
if (this.scene.panel.balloontip.el == this) {
|
||||
this.scene.panel.balloontip.hide();
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
var ctx = TD.ctx;
|
||||
|
||||
ctx.lineWidth = 2 * _TD.retina;
|
||||
ctx.fillStyle = this.is_hover ? "#eee" : "#ccc";
|
||||
ctx.strokeStyle = "#999";
|
||||
ctx.beginPath();
|
||||
ctx.rect(this.x, this.y, this.width, this.height);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.font = "normal " + (12 * _TD.retina) + "px 'Courier New'";
|
||||
ctx.beginPath();
|
||||
ctx.fillText(this.text, this.font_x, this.font_y);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param id {String}
|
||||
* @param cfg {Object} 配置对象
|
||||
* 至少需要包含以下项:
|
||||
* {
|
||||
* x:
|
||||
* y:
|
||||
* text:
|
||||
* onClick: function
|
||||
* sence:
|
||||
* }
|
||||
*/
|
||||
TD.Button = function (id, cfg) {
|
||||
cfg.on_events = ["enter", "out", "click"];
|
||||
var button = new TD.Element(id, cfg);
|
||||
TD.lang.mix(button, button_obj);
|
||||
button._init(cfg);
|
||||
|
||||
return button;
|
||||
};
|
||||
|
||||
|
||||
// gameover 对象的属性、方法。注意属性中不要有数组、对象等
|
||||
// 引用属性,否则多个实例的相关属性会发生冲突
|
||||
var gameover_obj = {
|
||||
_init: function (cfg) {
|
||||
this.panel = cfg.panel;
|
||||
this.scene = cfg.scene;
|
||||
this._score_submitted = false; // 添加标志,确保只提交一次成绩
|
||||
|
||||
this.addToScene(this.scene, 1, 9);
|
||||
},
|
||||
render: function () {
|
||||
|
||||
this.panel.btn_pause.hide();
|
||||
this.panel.btn_upgrade.hide();
|
||||
this.panel.btn_sell.hide();
|
||||
this.panel.btn_restart.show();
|
||||
|
||||
var ctx = TD.ctx;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillStyle = "#ccc";
|
||||
ctx.font = "bold 62px 'Verdana'";
|
||||
ctx.beginPath();
|
||||
ctx.fillText("GAME OVER", this.width / 2, this.height / 2);
|
||||
ctx.closePath();
|
||||
|
||||
// 尝试将成绩通过 postMessage 通知父窗口(若被嵌入在 iframe 中)
|
||||
// 仅在第一次渲染时发送,防止重复提交
|
||||
if (!this._score_submitted) {
|
||||
this._score_submitted = true;
|
||||
try {
|
||||
var timeSeconds = 0;
|
||||
if (typeof TD.startedAt === 'number') {
|
||||
timeSeconds = Math.round(((new Date()).getTime() - TD.startedAt) / 1000);
|
||||
}
|
||||
var wave = (this.scene && typeof this.scene.wave === 'number') ? this.scene.wave : (TD && TD.stage && TD.stage.current_scene ? TD.stage.current_scene.wave : 0);
|
||||
var score = typeof TD.score === 'number' ? TD.score : 0;
|
||||
if (window && window.parent && window !== window.parent) {
|
||||
window.parent.postMessage({
|
||||
type: 'tower-defense:complete',
|
||||
wave: wave,
|
||||
score: score,
|
||||
timeSeconds: timeSeconds
|
||||
}, '*');
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略 postMessage 错误
|
||||
console.error('Failed to send tower defense score:', e);
|
||||
}
|
||||
}
|
||||
ctx.fillStyle = "#f00";
|
||||
ctx.font = "bold 60px 'Verdana'";
|
||||
ctx.beginPath();
|
||||
ctx.fillText("GAME OVER", this.width / 2, this.height / 2);
|
||||
ctx.closePath();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param id {String}
|
||||
* @param cfg {Object} 配置对象
|
||||
* 至少需要包含以下项:
|
||||
* {
|
||||
* panel:
|
||||
* scene:
|
||||
* }
|
||||
*/
|
||||
TD.GameOver = function (id, cfg) {
|
||||
var obj = new TD.Element(id, cfg);
|
||||
TD.lang.mix(obj, gameover_obj);
|
||||
obj._init(cfg);
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 恢复 n 点生命值
|
||||
* @param n
|
||||
*/
|
||||
TD.recover = function (n) {
|
||||
// TD.life += n;
|
||||
TD.life_recover = n;
|
||||
TD.log("life recover: " + n);
|
||||
};
|
||||
|
||||
// 滑动条控件
|
||||
var slider_obj = {
|
||||
_init: function (cfg) {
|
||||
cfg = cfg || {};
|
||||
this.x = cfg.x || 0;
|
||||
this.y = cfg.y || 0;
|
||||
this.width = cfg.width || 80 * _TD.retina;
|
||||
this.height = cfg.height || 10 * _TD.retina;
|
||||
this.min = cfg.min || 0;
|
||||
this.max = cfg.max || 10;
|
||||
this.value = cfg.value || 0;
|
||||
this.onChange = cfg.onChange || function(){};
|
||||
this.scene = cfg.scene;
|
||||
this.is_hover = false;
|
||||
|
||||
this.addToScene(this.scene, this.step_level, this.render_level);
|
||||
},
|
||||
step: function () {
|
||||
// 检查鼠标是否在滑动条上
|
||||
if (TD.eventManager.isOn(this)) {
|
||||
this.is_hover = true;
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
var ctx = TD.ctx;
|
||||
var track_y = this.y + this.height / 2;
|
||||
|
||||
// 绘制轨道
|
||||
ctx.fillStyle = "#ddd";
|
||||
ctx.fillRect(this.x, track_y - 2, this.width, 4);
|
||||
|
||||
// 绘制已填充部分
|
||||
var fill_width = (this.value - this.min) / (this.max - this.min) * this.width;
|
||||
ctx.fillStyle = "#4a90e2";
|
||||
ctx.fillRect(this.x, track_y - 2, fill_width, 4);
|
||||
|
||||
// 绘制滑块
|
||||
var knob_x = this.x + fill_width;
|
||||
ctx.fillStyle = this.is_hover ? "#2563eb" : "#4a90e2";
|
||||
ctx.beginPath();
|
||||
ctx.arc(knob_x, track_y, 6 * _TD.retina, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// 绘制标签
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.font = "normal " + (10 * _TD.retina) + "px 'Arial'";
|
||||
|
||||
// 显示速度标签
|
||||
var labels = ["1x", "2x", "4x", "8x", "16x"];
|
||||
for (var i = 0; i <= this.max; i++) {
|
||||
var label_x = this.x + (i / this.max) * this.width;
|
||||
ctx.fillText(labels[i] || (Math.pow(2, i) + "x"), label_x, track_y + 12);
|
||||
|
||||
// 绘制刻度线
|
||||
ctx.strokeStyle = "#ccc";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(label_x, track_y - 5);
|
||||
ctx.lineTo(label_x, track_y + 5);
|
||||
ctx.stroke();
|
||||
}
|
||||
},
|
||||
onEnter: function () {
|
||||
this.is_hover = true;
|
||||
TD.mouseHand(true);
|
||||
},
|
||||
onOut: function () {
|
||||
this.is_hover = false;
|
||||
TD.mouseHand(false);
|
||||
},
|
||||
onClick: function () {
|
||||
// 点击滑动条时更新值
|
||||
if (TD.eventManager.ex !== undefined && TD.eventManager.ey !== undefined) {
|
||||
var relative_x = TD.eventManager.ex - this.x;
|
||||
var new_value = Math.round((relative_x / this.width) * (this.max - this.min) + this.min);
|
||||
new_value = Math.max(this.min, Math.min(this.max, new_value));
|
||||
if (new_value !== this.value) {
|
||||
this.value = new_value;
|
||||
this.onChange(this.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TD.Slider = function (id, cfg) {
|
||||
cfg.on_events = ["enter", "out", "click"];
|
||||
var slider = new TD.Element(id, cfg);
|
||||
TD.lang.mix(slider, slider_obj);
|
||||
slider._init(cfg);
|
||||
return slider;
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
|
||||
240
html5-tower-defense-master/src/js/td-render-buildings.js
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* Last Update: 2011/1/10 5:22:52
|
||||
*/
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
function lineTo2(ctx, x0, y0, x1, y1, len) {
|
||||
var x2, y2, a, b, p, xt,
|
||||
a2, b2, c2;
|
||||
|
||||
if (x0 == x1) {
|
||||
x2 = x0;
|
||||
y2 = y1 > y0 ? y0 + len : y0 - len;
|
||||
} else if (y0 == y1) {
|
||||
y2 = y0;
|
||||
x2 = x1 > x0 ? x0 + len : x0 - len;
|
||||
} else {
|
||||
// 解一元二次方程
|
||||
a = (y0 - y1) / (x0 - x1);
|
||||
b = y0 - x0 * a;
|
||||
a2 = a * a + 1;
|
||||
b2 = 2 * (a * (b - y0) - x0);
|
||||
c2 = Math.pow(b - y0, 2) + x0 * x0 - Math.pow(len, 2);
|
||||
p = Math.pow(b2, 2) - 4 * a2 * c2;
|
||||
if (p < 0) {
|
||||
// TD.log("ERROR: [a, b, len] = [" + ([a, b, len]).join(", ") + "]");
|
||||
return [0, 0];
|
||||
}
|
||||
p = Math.sqrt(p);
|
||||
xt = (-b2 + p) / (2 * a2);
|
||||
if ((x1 - x0 > 0 && xt - x0 > 0) ||
|
||||
(x1 - x0 < 0 && xt - x0 < 0)) {
|
||||
x2 = xt;
|
||||
y2 = a * x2 + b;
|
||||
} else {
|
||||
x2 = (-b2 - p) / (2 * a2);
|
||||
y2 = a * x2 + b;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.lineCap = "round";
|
||||
ctx.moveTo(x0, y0);
|
||||
ctx.lineTo(x2, y2);
|
||||
|
||||
return [x2, y2];
|
||||
}
|
||||
|
||||
var renderFunctions = {
|
||||
"cannon": function (b, ctx, map, gs, gs2) {
|
||||
var target_position = b.getTargetPosition();
|
||||
|
||||
ctx.fillStyle = "#393";
|
||||
ctx.strokeStyle = "#000";
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = _TD.retina;
|
||||
ctx.arc(b.cx, b.cy, gs2 - 5, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.lineWidth = 3 * _TD.retina;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(b.cx, b.cy);
|
||||
b.muzzle = lineTo2(ctx, b.cx, b.cy, target_position[0], target_position[1], gs2);
|
||||
ctx.closePath();
|
||||
// ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.lineWidth = _TD.retina;
|
||||
ctx.fillStyle = "#060";
|
||||
ctx.beginPath();
|
||||
ctx.arc(b.cx, b.cy, 7 * _TD.retina, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = "#cec";
|
||||
ctx.beginPath();
|
||||
ctx.arc(b.cx + 2, b.cy - 2, 3 * _TD.retina, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
},
|
||||
"LMG": function (b, ctx, map, gs, gs2) {
|
||||
var target_position = b.getTargetPosition();
|
||||
|
||||
ctx.fillStyle = "#36f";
|
||||
ctx.strokeStyle = "#000";
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = _TD.retina;
|
||||
ctx.arc(b.cx, b.cy, 7 * _TD.retina, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.lineWidth = 2 * _TD.retina;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(b.cx, b.cy);
|
||||
b.muzzle = lineTo2(ctx, b.cx, b.cy, target_position[0], target_position[1], gs2);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.lineWidth = _TD.retina;
|
||||
ctx.fillStyle = "#66c";
|
||||
ctx.beginPath();
|
||||
ctx.arc(b.cx, b.cy, 5 * _TD.retina, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = "#ccf";
|
||||
ctx.beginPath();
|
||||
ctx.arc(b.cx + 1, b.cy - 1, 2 * _TD.retina, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
},
|
||||
"HMG": function (b, ctx, map, gs, gs2) {
|
||||
var target_position = b.getTargetPosition();
|
||||
|
||||
ctx.fillStyle = "#933";
|
||||
ctx.strokeStyle = "#000";
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = _TD.retina;
|
||||
ctx.arc(b.cx, b.cy, gs2 - 2, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.lineWidth = 5 * _TD.retina;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(b.cx, b.cy);
|
||||
b.muzzle = lineTo2(ctx, b.cx, b.cy, target_position[0], target_position[1], gs2);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.lineWidth = _TD.retina;
|
||||
ctx.fillStyle = "#630";
|
||||
ctx.beginPath();
|
||||
ctx.arc(b.cx, b.cy, gs2 - 5 * _TD.retina, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = "#960";
|
||||
ctx.beginPath();
|
||||
ctx.arc(b.cx + 1, b.cy - 1, 8 * _TD.retina, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = "#fcc";
|
||||
ctx.beginPath();
|
||||
ctx.arc(b.cx + 3, b.cy - 3, 4 * _TD.retina, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
},
|
||||
"wall": function (b, ctx, map, gs, gs2) {
|
||||
ctx.lineWidth = _TD.retina;
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.strokeStyle = "#000";
|
||||
ctx.fillRect(b.cx - gs2 + 1, b.cy - gs2 + 1, gs - 1, gs - 1);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(b.cx - gs2 + 0.5, b.cy - gs2 + 0.5);
|
||||
ctx.lineTo(b.cx - gs2 + 0.5, b.cy + gs2 + 0.5);
|
||||
ctx.lineTo(b.cx + gs2 + 0.5, b.cy + gs2 + 0.5);
|
||||
ctx.lineTo(b.cx + gs2 + 0.5, b.cy - gs2 + 0.5);
|
||||
ctx.lineTo(b.cx - gs2 + 0.5, b.cy - gs2 + 0.5);
|
||||
ctx.moveTo(b.cx - gs2 + 0.5, b.cy + gs2 + 0.5);
|
||||
ctx.lineTo(b.cx + gs2 + 0.5, b.cy - gs2 + 0.5);
|
||||
ctx.moveTo(b.cx - gs2 + 0.5, b.cy - gs2 + 0.5);
|
||||
ctx.lineTo(b.cx + gs2 + 0.5, b.cy + gs2 + 0.5);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
},
|
||||
"laser_gun": function (b, ctx/*, map, gs, gs2*/) {
|
||||
// var target_position = b.getTargetPosition();
|
||||
|
||||
ctx.fillStyle = "#f00";
|
||||
ctx.strokeStyle = "#000";
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = _TD.retina;
|
||||
// ctx.arc(b.cx, b.cy, gs2 - 5, 0, Math.PI * 2, true);
|
||||
ctx.moveTo(b.cx, b.cy - 10 * _TD.retina);
|
||||
ctx.lineTo(b.cx - 8.66 * _TD.retina, b.cy + 5 * _TD.retina);
|
||||
ctx.lineTo(b.cx + 8.66 * _TD.retina, b.cy + 5 * _TD.retina);
|
||||
ctx.lineTo(b.cx, b.cy - 10 * _TD.retina);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = "#60f";
|
||||
ctx.beginPath();
|
||||
ctx.arc(b.cx, b.cy, 7 * _TD.retina, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.beginPath();
|
||||
ctx.arc(b.cx, b.cy, 3 * _TD.retina, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.beginPath();
|
||||
ctx.arc(b.cx + 1, b.cy - 1, _TD.retina, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
ctx.lineWidth = 3 * _TD.retina;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(b.cx, b.cy);
|
||||
// b.muzzle = lineTo2(ctx, b.cx, b.cy, target_position[0], target_position[1], gs2);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
};
|
||||
|
||||
TD.renderBuilding = function (building) {
|
||||
var ctx = TD.ctx,
|
||||
map = building.map,
|
||||
gs = TD.grid_size,
|
||||
gs2 = TD.grid_size / 2;
|
||||
|
||||
(renderFunctions[building.type] || renderFunctions["wall"])(
|
||||
building, ctx, map, gs, gs2
|
||||
);
|
||||
}
|
||||
|
||||
}); // _TD.a.push end
|
||||
294
html5-tower-defense-master/src/js/td-save-load-fix.js
Normal file
@@ -0,0 +1,294 @@
|
||||
// Expose a small API for parent page to save/load minimal game state.
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.TD = TD;
|
||||
window.__TD_getState = function () {
|
||||
try {
|
||||
console.log('[__TD_getState] ===== START SAVING STATE =====');
|
||||
|
||||
// 直接从当前 scene 获取 map,这是游戏初始化时的做法
|
||||
var scene = TD.stage && TD.stage.current_act && TD.stage.current_act.current_scene;
|
||||
console.log('[__TD_getState] scene:', scene);
|
||||
console.log('[__TD_getState] scene.map:', scene ? scene.map : 'scene is null');
|
||||
|
||||
var map = scene && scene.map;
|
||||
console.log('[__TD_getState] map:', map);
|
||||
|
||||
var buildings = [];
|
||||
if (map && map.buildings && map.buildings.length) {
|
||||
console.log('[__TD_getState] Found ' + map.buildings.length + ' buildings');
|
||||
for (var i = 0; i < map.buildings.length; i++) {
|
||||
var b = map.buildings[i];
|
||||
if (!b || !b.grid) continue;
|
||||
buildings.push({ type: b.type, mx: b.grid.mx, my: b.grid.my, level: b.level, money: b.money });
|
||||
}
|
||||
}
|
||||
|
||||
// 保存怪物状态 - 从 map.monsters 获取(这是游戏原始的存储方式)
|
||||
var monsters_data = [];
|
||||
if (map && map.monsters && map.monsters.length) {
|
||||
console.log('[__TD_getState] Found ' + map.monsters.length + ' monsters in map.monsters');
|
||||
for (var i = 0; i < map.monsters.length; i++) {
|
||||
var m = map.monsters[i];
|
||||
console.log('[__TD_getState] Monster ' + i + ':', {
|
||||
'is_valid': m ? m.is_valid : 'null',
|
||||
'grid': m ? m.grid : 'null',
|
||||
'idx': m ? m.idx : 'null',
|
||||
'life': m ? m.life : 'null'
|
||||
});
|
||||
|
||||
if (!m || !m.is_valid || !m.grid) {
|
||||
console.log('[__TD_getState] Skipping invalid monster', m);
|
||||
continue;
|
||||
}
|
||||
|
||||
var monster_data = {
|
||||
// 核心属性
|
||||
idx: m.idx,
|
||||
difficulty: m.difficulty,
|
||||
life: m.life,
|
||||
life0: m.life0,
|
||||
shield: m.shield,
|
||||
speed: m.speed,
|
||||
damage: m.damage,
|
||||
money: m.money,
|
||||
|
||||
// 位置和渲染
|
||||
mx: m.grid.mx,
|
||||
my: m.grid.my,
|
||||
cx: m.cx,
|
||||
cy: m.cy,
|
||||
r: m.r,
|
||||
color: m.color,
|
||||
|
||||
// 路径和导航
|
||||
toward: m.toward,
|
||||
way: m.way || [],
|
||||
next_grid_mx: m.next_grid ? m.next_grid.mx : null,
|
||||
next_grid_my: m.next_grid ? m.next_grid.my : null,
|
||||
|
||||
// 移动辅助
|
||||
_dx: m._dx || 0,
|
||||
_dy: m._dy || 0,
|
||||
|
||||
// 配置级别
|
||||
step_level: m.step_level,
|
||||
render_level: m.render_level,
|
||||
|
||||
// 状态标志
|
||||
is_paused: m.is_paused,
|
||||
is_blocked: m.is_blocked
|
||||
};
|
||||
|
||||
monsters_data.push(monster_data);
|
||||
console.log('[__TD_getState] Saved monster data:', monster_data);
|
||||
}
|
||||
} else {
|
||||
console.log('[__TD_getState] No monsters found');
|
||||
console.log('[__TD_getState] map:', map);
|
||||
console.log('[__TD_getState] map.monsters:', map ? map.monsters : 'map is null');
|
||||
}
|
||||
|
||||
var state = {
|
||||
money: TD.money,
|
||||
life: TD.life,
|
||||
score: TD.score,
|
||||
wave: (scene && scene.wave) || 0,
|
||||
buildings: buildings,
|
||||
monsters: monsters_data
|
||||
};
|
||||
console.log('[__TD_getState] Returning state with ' + monsters_data.length + ' monsters');
|
||||
console.log('[__TD_getState] ===== END SAVING STATE =====');
|
||||
return state;
|
||||
} catch (e) {
|
||||
console.error('[__TD_getState] error', e);
|
||||
console.error('[__TD_getState] error stack', e.stack);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
window.__TD_loadState = function (s) {
|
||||
try {
|
||||
console.log('[__TD_loadState] ===== START LOADING STATE =====');
|
||||
console.log('[__TD_loadState] State received:', s);
|
||||
|
||||
if (!s) {
|
||||
console.warn('[__TD_loadState] State is null or undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接从当前 scene 获取 map
|
||||
var scene = TD.stage && TD.stage.current_act && TD.stage.current_act.current_scene;
|
||||
console.log('[__TD_loadState] scene:', scene);
|
||||
|
||||
var map = scene && scene.map;
|
||||
if (!map) {
|
||||
console.warn('[__TD_loadState] Map is null');
|
||||
return;
|
||||
}
|
||||
console.log('[__TD_loadState] map:', map);
|
||||
console.log('[__TD_loadState] Current monsters count:', map.monsters ? map.monsters.length : 'null');
|
||||
|
||||
// remove existing buildings
|
||||
for (var i = 0; i < map.grids.length; i++) {
|
||||
var g = map.grids[i];
|
||||
if (g && g.building) g.removeBuilding();
|
||||
}
|
||||
console.log('[__TD_loadState] Removed existing buildings');
|
||||
|
||||
// remove existing monsters
|
||||
if (map.monsters && map.monsters.length) {
|
||||
console.log('[__TD_loadState] Removing ' + map.monsters.length + ' existing monsters');
|
||||
for (var mi = 0; mi < map.monsters.length; mi++) {
|
||||
var old_monster = map.monsters[mi];
|
||||
if (old_monster) {
|
||||
old_monster.pause();
|
||||
old_monster.del();
|
||||
}
|
||||
}
|
||||
map.monsters = [];
|
||||
console.log('[__TD_loadState] Cleared monsters array');
|
||||
}
|
||||
|
||||
// add saved buildings
|
||||
if (s.buildings && s.buildings.length) {
|
||||
console.log('[__TD_loadState] Restoring ' + s.buildings.length + ' buildings');
|
||||
for (var j = 0; j < s.buildings.length; j++) {
|
||||
var bi = s.buildings[j];
|
||||
var grid = map.getGrid(bi.mx, bi.my);
|
||||
if (grid) grid.addBuilding(bi.type);
|
||||
var b = grid && grid.building;
|
||||
if (b) {
|
||||
if (typeof bi.level !== 'undefined') b.level = bi.level;
|
||||
if (typeof bi.money !== 'undefined') b.money = bi.money;
|
||||
b.updateBtnDesc && b.updateBtnDesc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add saved monsters
|
||||
if (s.monsters && s.monsters.length) {
|
||||
console.log('[__TD_loadState] Found ' + s.monsters.length + ' monsters to restore');
|
||||
for (var mj = 0; mj < s.monsters.length; mj++) {
|
||||
var md = s.monsters[mj];
|
||||
console.log('[__TD_loadState] Processing monster:', md);
|
||||
|
||||
// 验证生命值
|
||||
if (md.life <= 0) {
|
||||
console.warn('[__TD_loadState] Cannot restore monster: life is 0', md);
|
||||
continue;
|
||||
}
|
||||
if (md.life > md.life0) {
|
||||
md.life = md.life0; // 修正:当前生命值不应超过初始值
|
||||
}
|
||||
|
||||
var monster_grid = map.getGrid(md.mx, md.my);
|
||||
if (!monster_grid) {
|
||||
console.warn('[__TD_loadState] Cannot restore monster: grid not found', md.mx, md.my);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查怪物是否在终点
|
||||
if (monster_grid === map.exit) {
|
||||
console.warn('[__TD_loadState] Skipping monster at exit', md);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log('[__TD_loadState] Creating monster instance');
|
||||
// 创建怪物实例
|
||||
var monster = new TD.Monster(null, {
|
||||
idx: md.idx,
|
||||
difficulty: md.difficulty,
|
||||
step_level: md.step_level,
|
||||
render_level: md.render_level
|
||||
});
|
||||
console.log('[__TD_loadState] Monster instance created');
|
||||
|
||||
// 手动设置保存的属性(跳过_init中的随机计算)
|
||||
monster.life = md.life;
|
||||
monster.life0 = md.life0;
|
||||
monster.shield = md.shield;
|
||||
monster.speed = md.speed;
|
||||
monster.damage = md.damage;
|
||||
monster.money = md.money;
|
||||
monster.r = md.r;
|
||||
monster.color = md.color;
|
||||
monster.toward = md.toward;
|
||||
monster._dx = md._dx;
|
||||
monster._dy = md._dy;
|
||||
monster.is_paused = md.is_paused;
|
||||
monster.is_blocked = md.is_blocked;
|
||||
console.log('[__TD_loadState] Monster properties set');
|
||||
|
||||
// 设置位置
|
||||
monster.beAddToGrid(monster_grid);
|
||||
monster.cx = md.cx;
|
||||
monster.cy = md.cy;
|
||||
monster.caculatePos();
|
||||
console.log('[__TD_loadState] Monster positioned at grid (' + md.mx + ',' + md.my + ') cx:' + monster.cx + ' cy:' + monster.cy);
|
||||
|
||||
// 设置下一个目标格子
|
||||
if (md.next_grid_mx !== null && md.next_grid_my !== null) {
|
||||
monster.next_grid = map.getGrid(md.next_grid_mx, md.next_grid_my);
|
||||
console.log('[__TD_loadState] Monster next_grid set to (' + md.next_grid_mx + ',' + md.next_grid_my + ')');
|
||||
}
|
||||
|
||||
// 验证并过滤路径点
|
||||
var valid_way = [];
|
||||
for (var wi = 0; wi < md.way.length; wi++) {
|
||||
var wp = md.way[wi];
|
||||
var wp_grid = map.getGrid(wp[0], wp[1]);
|
||||
if (wp_grid) {
|
||||
valid_way.push(wp);
|
||||
}
|
||||
}
|
||||
monster.way = valid_way;
|
||||
console.log('[__TD_loadState] Monster way set with ' + valid_way.length + ' waypoints');
|
||||
|
||||
// 如果路径被严重破坏,触发重新寻路
|
||||
if (valid_way.length === 0) {
|
||||
monster.findWay();
|
||||
console.log('[__TD_loadState] Monster found new path after way was empty');
|
||||
}
|
||||
|
||||
// 恢复后检查怪物是否被阻塞
|
||||
if (monster.is_blocked || !monster.next_grid) {
|
||||
monster.findWay();
|
||||
if (!monster.next_grid) {
|
||||
monster.beBlocked();
|
||||
}
|
||||
console.log('[__TD_loadState] Monster path checked, next_grid:', monster.next_grid);
|
||||
}
|
||||
|
||||
// 添加到场景和地图
|
||||
monster_grid.scene.addElement(monster, monster.step_level, monster.render_level);
|
||||
map.monsters.push(monster);
|
||||
console.log('[__TD_loadState] Monster added, total monsters:', map.monsters.length);
|
||||
|
||||
// 如果未被暂停,启动怪物
|
||||
if (!monster.is_paused) {
|
||||
monster.start();
|
||||
console.log('[__TD_loadState] Monster started');
|
||||
}
|
||||
}
|
||||
console.log('[__TD_loadState] Finished restoring monsters, final count:', map.monsters.length);
|
||||
} else {
|
||||
console.log('[__TD_loadState] No monsters to restore');
|
||||
}
|
||||
|
||||
if (typeof s.money !== 'undefined') TD.money = s.money;
|
||||
if (typeof s.life !== 'undefined') TD.life = s.life;
|
||||
if (typeof s.score !== 'undefined') TD.score = s.score;
|
||||
if (typeof s.wave !== 'undefined' && scene) scene.wave = s.wave;
|
||||
|
||||
console.log('[__TD_loadState] Load completed. Money:' + TD.money + ' Life:' + TD.life + ' Score:' + TD.score);
|
||||
console.log('[__TD_loadState] ===== END LOADING STATE =====');
|
||||
} catch (e) {
|
||||
console.error('[__TD_loadState] error', e);
|
||||
console.error('[__TD_loadState] error stack', e.stack);
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('expose TD api failed', e);
|
||||
}
|
||||
289
html5-tower-defense-master/src/js/td-save-load-monsters.js
Normal file
@@ -0,0 +1,289 @@
|
||||
// 新的保存和加载函数,添加怪物状态保存/恢复功能
|
||||
// 替换原有的 __TD_getState 和 __TD_loadState 函数
|
||||
|
||||
window.__TD_getState = function () {
|
||||
try {
|
||||
console.log('[__TD_getState] ===== START SAVING STATE =====');
|
||||
|
||||
// 直接从当前 scene 获取 map
|
||||
var scene = TD.stage && TD.stage.current_act && TD.stage.current_act.current_scene;
|
||||
console.log('[__TD_getState] scene:', scene);
|
||||
console.log('[__TD_getState] scene.map:', scene ? scene.map : 'scene is null');
|
||||
|
||||
var map = scene && scene.map;
|
||||
console.log('[__TD_getState] map:', map);
|
||||
|
||||
var buildings = [];
|
||||
if (map && map.buildings && map.buildings.length) {
|
||||
console.log('[__TD_getState] Found ' + map.buildings.length + ' buildings');
|
||||
for (var i = 0; i < map.buildings.length; i++) {
|
||||
var b = map.buildings[i];
|
||||
if (!b || !b.grid) continue;
|
||||
buildings.push({ type: b.type, mx: b.grid.mx, my: b.grid.my, level: b.level, money: b.money });
|
||||
}
|
||||
}
|
||||
|
||||
// 保存怪物状态 - 从 map.monsters 获取
|
||||
var monsters_data = [];
|
||||
if (map && map.monsters && map.monsters.length) {
|
||||
console.log('[__TD_getState] Found ' + map.monsters.length + ' monsters in map.monsters');
|
||||
for (var i = 0; i < map.monsters.length; i++) {
|
||||
var m = map.monsters[i];
|
||||
console.log('[__TD_getState] Monster ' + i + ':', {
|
||||
'is_valid': m ? m.is_valid : 'null',
|
||||
'grid': m ? m.grid : 'null',
|
||||
'idx': m ? m.idx : 'null',
|
||||
'life': m ? m.life : 'null'
|
||||
});
|
||||
|
||||
if (!m || !m.is_valid || !m.grid) {
|
||||
console.log('[__TD_getState] Skipping invalid monster', m);
|
||||
continue;
|
||||
}
|
||||
|
||||
var monster_data = {
|
||||
// 核心属性
|
||||
idx: m.idx,
|
||||
difficulty: m.difficulty,
|
||||
life: m.life,
|
||||
life0: m.life0,
|
||||
shield: m.shield,
|
||||
speed: m.speed,
|
||||
damage: m.damage,
|
||||
money: m.money,
|
||||
|
||||
// 位置和渲染
|
||||
mx: m.grid.mx,
|
||||
my: m.grid.my,
|
||||
cx: m.cx,
|
||||
cy: m.cy,
|
||||
r: m.r,
|
||||
color: m.color,
|
||||
|
||||
// 路径和导航
|
||||
toward: m.toward,
|
||||
way: m.way || [],
|
||||
next_grid_mx: m.next_grid ? m.next_grid.mx : null,
|
||||
next_grid_my: m.next_grid ? m.next_grid.my : null,
|
||||
|
||||
// 移动辅助
|
||||
_dx: m._dx || 0,
|
||||
_dy: m._dy || 0,
|
||||
|
||||
// 配置级别
|
||||
step_level: m.step_level,
|
||||
render_level: m.render_level,
|
||||
|
||||
// 状态标志
|
||||
is_paused: m.is_paused,
|
||||
is_blocked: m.is_blocked
|
||||
};
|
||||
|
||||
monsters_data.push(monster_data);
|
||||
console.log('[__TD_getState] Saved monster data:', monster_data);
|
||||
}
|
||||
} else {
|
||||
console.log('[__TD_getState] No monsters found');
|
||||
console.log('[__TD_getState] map:', map);
|
||||
console.log('[__TD_getState] map.monsters:', map ? map.monsters : 'map is null');
|
||||
}
|
||||
|
||||
var state = {
|
||||
money: TD.money,
|
||||
life: TD.life,
|
||||
score: TD.score,
|
||||
wave: (scene && scene.wave) || 0,
|
||||
buildings: buildings,
|
||||
monsters: monsters_data
|
||||
};
|
||||
console.log('[__TD_getState] Returning state with ' + monsters_data.length + ' monsters');
|
||||
console.log('[__TD_getState] ===== END SAVING STATE =====');
|
||||
return state;
|
||||
} catch (e) {
|
||||
console.error('[__TD_getState] error', e);
|
||||
console.error('[__TD_getState] error stack', e.stack);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
window.__TD_loadState = function (s) {
|
||||
try {
|
||||
console.log('[__TD_loadState] ===== START LOADING STATE =====');
|
||||
console.log('[__TD_loadState] State received:', s);
|
||||
|
||||
if (!s) {
|
||||
console.warn('[__TD_loadState] State is null or undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接从当前 scene 获取 map
|
||||
var scene = TD.stage && TD.stage.current_act && TD.stage.current_act.current_scene;
|
||||
console.log('[__TD_loadState] scene:', scene);
|
||||
|
||||
var map = scene && scene.map;
|
||||
if (!map) {
|
||||
console.warn('[__TD_loadState] Map is null');
|
||||
return;
|
||||
}
|
||||
console.log('[__TD_loadState] map:', map);
|
||||
console.log('[__TD_loadState] Current monsters count:', map.monsters ? map.monsters.length : 'null');
|
||||
|
||||
// remove existing buildings
|
||||
for (var i = 0; i < map.grids.length; i++) {
|
||||
var g = map.grids[i];
|
||||
if (g && g.building) g.removeBuilding();
|
||||
}
|
||||
console.log('[__TD_loadState] Removed existing buildings');
|
||||
|
||||
// remove existing monsters
|
||||
if (map.monsters && map.monsters.length) {
|
||||
console.log('[__TD_loadState] Removing ' + map.monsters.length + ' existing monsters');
|
||||
for (var mi = 0; mi < map.monsters.length; mi++) {
|
||||
var old_monster = map.monsters[mi];
|
||||
if (old_monster) {
|
||||
old_monster.pause();
|
||||
old_monster.del();
|
||||
}
|
||||
}
|
||||
map.monsters = [];
|
||||
console.log('[__TD_loadState] Cleared monsters array');
|
||||
}
|
||||
|
||||
// add saved buildings
|
||||
if (s.buildings && s.buildings.length) {
|
||||
console.log('[__TD_loadState] Restoring ' + s.buildings.length + ' buildings');
|
||||
for (var j = 0; j < s.buildings.length; j++) {
|
||||
var bi = s.buildings[j];
|
||||
var grid = map.getGrid(bi.mx, bi.my);
|
||||
if (grid) grid.addBuilding(bi.type);
|
||||
var b = grid && grid.building;
|
||||
if (b) {
|
||||
if (typeof bi.level !== 'undefined') b.level = bi.level;
|
||||
if (typeof bi.money !== 'undefined') b.money = bi.money;
|
||||
b.updateBtnDesc && b.updateBtnDesc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add saved monsters
|
||||
if (s.monsters && s.monsters.length) {
|
||||
console.log('[__TD_loadState] Found ' + s.monsters.length + ' monsters to restore');
|
||||
for (var mj = 0; mj < s.monsters.length; mj++) {
|
||||
var md = s.monsters[mj];
|
||||
console.log('[__TD_loadState] Processing monster:', md);
|
||||
|
||||
// 验证生命值
|
||||
if (md.life <= 0) {
|
||||
console.warn('[__TD_loadState] Cannot restore monster: life is 0', md);
|
||||
continue;
|
||||
}
|
||||
if (md.life > md.life0) {
|
||||
md.life = md.life0; // 修正:当前生命值不应超过初始值
|
||||
}
|
||||
|
||||
var monster_grid = map.getGrid(md.mx, md.my);
|
||||
if (!monster_grid) {
|
||||
console.warn('[__TD_loadState] Cannot restore monster: grid not found', md.mx, md.my);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查怪物是否在终点
|
||||
if (monster_grid === map.exit) {
|
||||
console.warn('[__TD_loadState] Skipping monster at exit', md);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log('[__TD_loadState] Creating monster instance');
|
||||
// 创建怪物实例
|
||||
var monster = new TD.Monster(null, {
|
||||
idx: md.idx,
|
||||
difficulty: md.difficulty,
|
||||
step_level: md.step_level,
|
||||
render_level: md.render_level
|
||||
});
|
||||
console.log('[__TD_loadState] Monster instance created');
|
||||
|
||||
// 手动设置保存的属性(跳过_init中的随机计算)
|
||||
monster.life = md.life;
|
||||
monster.life0 = md.life0;
|
||||
monster.shield = md.shield;
|
||||
monster.speed = md.speed;
|
||||
monster.damage = md.damage;
|
||||
monster.money = md.money;
|
||||
monster.r = md.r;
|
||||
monster.color = md.color;
|
||||
monster.toward = md.toward;
|
||||
monster._dx = md._dx;
|
||||
monster._dy = md._dy;
|
||||
monster.is_paused = md.is_paused;
|
||||
monster.is_blocked = md.is_blocked;
|
||||
console.log('[__TD_loadState] Monster properties set');
|
||||
|
||||
// 设置位置
|
||||
monster.beAddToGrid(monster_grid);
|
||||
monster.cx = md.cx;
|
||||
monster.cy = md.cy;
|
||||
monster.caculatePos();
|
||||
console.log('[__TD_loadState] Monster positioned at grid (' + md.mx + ',' + md.my + ') cx:' + monster.cx + ' cy:' + monster.cy);
|
||||
|
||||
// 设置下一个目标格子
|
||||
if (md.next_grid_mx !== null && md.next_grid_my !== null) {
|
||||
monster.next_grid = map.getGrid(md.next_grid_mx, md.next_grid_my);
|
||||
console.log('[__TD_loadState] Monster next_grid set to (' + md.next_grid_mx + ',' + md.next_grid_my + ')');
|
||||
}
|
||||
|
||||
// 验证并过滤路径点
|
||||
var valid_way = [];
|
||||
for (var wi = 0; wi < md.way.length; wi++) {
|
||||
var wp = md.way[wi];
|
||||
var wp_grid = map.getGrid(wp[0], wp[1]);
|
||||
if (wp_grid) {
|
||||
valid_way.push(wp);
|
||||
}
|
||||
}
|
||||
monster.way = valid_way;
|
||||
console.log('[__TD_loadState] Monster way set with ' + valid_way.length + ' waypoints');
|
||||
|
||||
// 如果路径被严重破坏,触发重新寻路
|
||||
if (valid_way.length === 0) {
|
||||
monster.findWay();
|
||||
console.log('[__TD_loadState] Monster found new path after way was empty');
|
||||
}
|
||||
|
||||
// 恢复后检查怪物是否被阻塞
|
||||
if (monster.is_blocked || !monster.next_grid) {
|
||||
monster.findWay();
|
||||
if (!monster.next_grid) {
|
||||
monster.beBlocked();
|
||||
}
|
||||
console.log('[__TD_loadState] Monster path checked, next_grid:', monster.next_grid);
|
||||
}
|
||||
|
||||
// 添加到场景和地图
|
||||
monster_grid.scene.addElement(monster, monster.step_level, monster.render_level);
|
||||
map.monsters.push(monster);
|
||||
console.log('[__TD_loadState] Monster added, total monsters:', map.monsters.length);
|
||||
|
||||
// 如果未被暂停,启动怪物
|
||||
if (!monster.is_paused) {
|
||||
monster.start();
|
||||
console.log('[__TD_loadState] Monster started');
|
||||
}
|
||||
}
|
||||
console.log('[__TD_loadState] Finished restoring monsters, final count:', map.monsters.length);
|
||||
} else {
|
||||
console.log('[__TD_loadState] No monsters to restore');
|
||||
}
|
||||
|
||||
if (typeof s.money !== 'undefined') TD.money = s.money;
|
||||
if (typeof s.life !== 'undefined') TD.life = s.life;
|
||||
if (typeof s.score !== 'undefined') TD.score = s.score;
|
||||
if (typeof s.wave !== 'undefined' && scene) scene.wave = s.wave;
|
||||
|
||||
console.log('[__TD_loadState] Load completed. Money:' + TD.money + ' Life:' + TD.life + ' Score:' + TD.score);
|
||||
console.log('[__TD_loadState] ===== END LOADING STATE =====');
|
||||
} catch (e) {
|
||||
console.error('[__TD_loadState] error', e);
|
||||
console.error('[__TD_loadState] error stack', e.stack);
|
||||
}
|
||||
};
|
||||
362
html5-tower-defense-master/src/js/td-stage.js
Normal file
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* Last Update: 2011/1/10 5:22:52
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
/**
|
||||
* 舞台类
|
||||
* @param id {String} 舞台ID
|
||||
* @param cfg {Object} 配置
|
||||
*/
|
||||
TD.Stage = function (id, cfg) {
|
||||
this.id = id || ("stage-" + TD.lang.rndStr());
|
||||
this.cfg = cfg || {};
|
||||
this.width = this.cfg.width || 640;
|
||||
this.height = this.cfg.height || 540;
|
||||
|
||||
/**
|
||||
* mode 有以下状态:
|
||||
* "normal": 普通状态
|
||||
* "build": 建造模式
|
||||
*/
|
||||
this.mode = "normal";
|
||||
|
||||
/*
|
||||
* state 有以下几种状态:
|
||||
* 0: 等待中
|
||||
* 1: 运行中
|
||||
* 2: 暂停
|
||||
* 3: 已结束
|
||||
*/
|
||||
this.state = 0;
|
||||
this.acts = [];
|
||||
this.current_act = null;
|
||||
this._step2 = TD.lang.nullFunc;
|
||||
|
||||
this._init();
|
||||
};
|
||||
|
||||
TD.Stage.prototype = {
|
||||
_init: function () {
|
||||
if (typeof this.cfg.init == "function") {
|
||||
this.cfg.init.call(this);
|
||||
}
|
||||
if (typeof this.cfg.step2 == "function") {
|
||||
this._step2 = this.cfg.step2;
|
||||
}
|
||||
},
|
||||
start: function () {
|
||||
this.state = 1;
|
||||
TD.lang.each(this.acts, function (obj) {
|
||||
obj.start();
|
||||
});
|
||||
},
|
||||
pause: function () {
|
||||
this.state = 2;
|
||||
},
|
||||
gameover: function () {
|
||||
//this.pause();
|
||||
this.current_act.gameover();
|
||||
},
|
||||
/**
|
||||
* 清除本 stage 所有物品
|
||||
*/
|
||||
clear: function () {
|
||||
this.state = 3;
|
||||
TD.lang.each(this.acts, function (obj) {
|
||||
obj.clear();
|
||||
});
|
||||
// delete this;
|
||||
},
|
||||
/**
|
||||
* 主循环函数
|
||||
*/
|
||||
step: function () {
|
||||
if (this.state != 1 || !this.current_act) return;
|
||||
TD.eventManager.step();
|
||||
this.current_act.step();
|
||||
|
||||
this._step2();
|
||||
},
|
||||
/**
|
||||
* 绘制函数
|
||||
*/
|
||||
render: function () {
|
||||
if (this.state == 0 || this.state == 3 || !this.current_act) return;
|
||||
this.current_act.render();
|
||||
},
|
||||
addAct: function (act) {
|
||||
this.acts.push(act);
|
||||
},
|
||||
addElement: function (el, step_level, render_level) {
|
||||
if (this.current_act)
|
||||
this.current_act.addElement(el, step_level, render_level);
|
||||
}
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
TD.Act = function (stage, id) {
|
||||
this.stage = stage;
|
||||
this.id = id || ("act-" + TD.lang.rndStr());
|
||||
|
||||
/*
|
||||
* state 有以下几种状态:
|
||||
* 0: 等待中
|
||||
* 1: 运行中
|
||||
* 2: 暂停
|
||||
* 3: 已结束
|
||||
*/
|
||||
this.state = 0;
|
||||
this.scenes = [];
|
||||
this.end_queue = []; // 本 act 结束后要执行的队列,添加时请保证里面全是函数
|
||||
this.current_scene = null;
|
||||
|
||||
this._init();
|
||||
};
|
||||
|
||||
TD.Act.prototype = {
|
||||
_init: function () {
|
||||
this.stage.addAct(this);
|
||||
},
|
||||
/*
|
||||
* 开始当前 act
|
||||
*/
|
||||
start: function () {
|
||||
if (this.stage.current_act && this.stage.current_act.state != 3) {
|
||||
// queue...
|
||||
this.state = 0;
|
||||
this.stage.current_act.queue(this.start);
|
||||
return;
|
||||
}
|
||||
// start
|
||||
this.state = 1;
|
||||
this.stage.current_act = this;
|
||||
TD.lang.each(this.scenes, function (obj) {
|
||||
obj.start();
|
||||
});
|
||||
},
|
||||
pause: function () {
|
||||
this.state = 2;
|
||||
},
|
||||
end: function () {
|
||||
this.state = 3;
|
||||
var f;
|
||||
while (f = this.end_queue.shift()) {
|
||||
f();
|
||||
}
|
||||
this.stage.current_act = null;
|
||||
},
|
||||
queue: function (f) {
|
||||
this.end_queue.push(f);
|
||||
},
|
||||
clear: function () {
|
||||
this.state = 3;
|
||||
TD.lang.each(this.scenes, function (obj) {
|
||||
obj.clear();
|
||||
});
|
||||
// delete this;
|
||||
},
|
||||
step: function () {
|
||||
if (this.state != 1 || !this.current_scene) return;
|
||||
this.current_scene.step();
|
||||
},
|
||||
render: function () {
|
||||
if (this.state == 0 || this.state == 3 || !this.current_scene) return;
|
||||
this.current_scene.render();
|
||||
},
|
||||
addScene: function (scene) {
|
||||
this.scenes.push(scene);
|
||||
},
|
||||
addElement: function (el, step_level, render_level) {
|
||||
if (this.current_scene)
|
||||
this.current_scene.addElement(el, step_level, render_level);
|
||||
},
|
||||
gameover: function () {
|
||||
//this.is_paused = true;
|
||||
//this.is_gameover = true;
|
||||
this.current_scene.gameover();
|
||||
}
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
TD.Scene = function (act, id) {
|
||||
this.act = act;
|
||||
this.stage = act.stage;
|
||||
this.is_gameover = false;
|
||||
this.id = id || ("scene-" + TD.lang.rndStr());
|
||||
/*
|
||||
* state 有以下几种状态:
|
||||
* 0: 等待中
|
||||
* 1: 运行中
|
||||
* 2: 暂停
|
||||
* 3: 已结束
|
||||
*/
|
||||
this.state = 0;
|
||||
this.end_queue = []; // 本 scene 结束后要执行的队列,添加时请保证里面全是函数
|
||||
this._step_elements = [
|
||||
// step 共分为 3 层
|
||||
[],
|
||||
// 0
|
||||
[],
|
||||
// 1 默认
|
||||
[] // 2
|
||||
];
|
||||
this._render_elements = [ // 渲染共分为 10 层
|
||||
[], // 0 背景 1 背景图片
|
||||
[], // 1 背景 2
|
||||
[], // 2 背景 3 地图、格子
|
||||
[], // 3 地面 1 一般建筑
|
||||
[], // 4 地面 2 人物、NPC等
|
||||
[], // 5 地面 3
|
||||
[], // 6 天空 1 子弹等
|
||||
[], // 7 天空 2 主地图外边的遮罩,panel
|
||||
[], // 8 天空 3
|
||||
[] // 9 系统特殊操作,如选中高亮,提示、文字遮盖等
|
||||
];
|
||||
|
||||
this._init();
|
||||
};
|
||||
|
||||
TD.Scene.prototype = {
|
||||
_init: function () {
|
||||
this.act.addScene(this);
|
||||
this.wave = 0; // 第几波
|
||||
},
|
||||
start: function () {
|
||||
if (this.act.current_scene &&
|
||||
this.act.current_scene != this &&
|
||||
this.act.current_scene.state != 3) {
|
||||
// queue...
|
||||
this.state = 0;
|
||||
this.act.current_scene.queue(this.start);
|
||||
return;
|
||||
}
|
||||
// start
|
||||
this.state = 1;
|
||||
this.act.current_scene = this;
|
||||
},
|
||||
pause: function () {
|
||||
this.state = 2;
|
||||
},
|
||||
end: function () {
|
||||
this.state = 3;
|
||||
var f;
|
||||
while (f = this.end_queue.shift()) {
|
||||
f();
|
||||
}
|
||||
this.clear();
|
||||
this.act.current_scene = null;
|
||||
},
|
||||
/**
|
||||
* 清空场景
|
||||
*/
|
||||
clear: function () {
|
||||
// 清空本 scene 中引用的所有对象以回收内存
|
||||
TD.lang.shift(this._step_elements, function (obj) {
|
||||
TD.lang.shift(obj, function (obj2) {
|
||||
// element
|
||||
//delete this.scene;
|
||||
obj2.del();
|
||||
// delete this;
|
||||
});
|
||||
// delete this;
|
||||
});
|
||||
TD.lang.shift(this._render_elements, function (obj) {
|
||||
TD.lang.shift(obj, function (obj2) {
|
||||
// element
|
||||
//delete this.scene;
|
||||
obj2.del();
|
||||
// delete this;
|
||||
});
|
||||
// delete this;
|
||||
});
|
||||
// delete this;
|
||||
},
|
||||
queue: function (f) {
|
||||
this.end_queue.push(f);
|
||||
},
|
||||
gameover: function () {
|
||||
if (this.is_gameover) return;
|
||||
this.pause();
|
||||
this.is_gameover = true;
|
||||
},
|
||||
step: function () {
|
||||
if (this.state != 1) return;
|
||||
if (TD.life <= 0) {
|
||||
TD.life = 0;
|
||||
this.gameover();
|
||||
}
|
||||
|
||||
var i, a;
|
||||
for (i = 0; i < 3; i++) {
|
||||
a = [];
|
||||
var level_elements = this._step_elements[i];
|
||||
TD.lang.shift(level_elements, function (obj) {
|
||||
if (obj.is_valid) {
|
||||
if (!obj.is_paused)
|
||||
obj.step();
|
||||
a.push(obj);
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
obj = null;
|
||||
}, 500); // 一会儿之后将这个对象彻底删除以收回内存
|
||||
}
|
||||
});
|
||||
this._step_elements[i] = a;
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
if (this.state == 0 || this.state == 3) return;
|
||||
var i, a,
|
||||
ctx = TD.ctx;
|
||||
|
||||
ctx.clearRect(0, 0, this.stage.width, this.stage.height);
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
a = [];
|
||||
var level_elements = this._render_elements[i];
|
||||
TD.lang.shift(level_elements, function (obj) {
|
||||
if (obj.is_valid) {
|
||||
if (obj.is_visiable)
|
||||
obj.render();
|
||||
a.push(obj);
|
||||
}
|
||||
});
|
||||
this._render_elements[i] = a;
|
||||
}
|
||||
|
||||
if (this.is_gameover) {
|
||||
this.panel.gameover_obj.show();
|
||||
}
|
||||
},
|
||||
addElement: function (el, step_level, render_level) {
|
||||
//TD.log([step_level, render_level]);
|
||||
step_level = step_level || el.step_level || 1;
|
||||
render_level = render_level || el.render_level;
|
||||
this._step_elements[step_level].push(el);
|
||||
this._render_elements[render_level].push(el);
|
||||
el.scene = this;
|
||||
el.step_level = step_level;
|
||||
el.render_level = render_level;
|
||||
}
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
224
html5-tower-defense-master/src/js/td-walk.js
Normal file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
* Last Update: 2011/1/10 5:22:52
|
||||
*/
|
||||
|
||||
|
||||
// _TD.a.push begin
|
||||
_TD.a.push(function (TD) {
|
||||
|
||||
/**
|
||||
* 使用 A* 算法(Dijkstra算法?)寻找从 (x1, y1) 到 (x2, y2) 最短的路线
|
||||
*
|
||||
*/
|
||||
TD.FindWay = function (w, h, x1, y1, x2, y2, f_passable) {
|
||||
this.m = [];
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
this.x1 = x1;
|
||||
this.y1 = y1;
|
||||
this.x2 = x2;
|
||||
this.y2 = y2;
|
||||
this.way = [];
|
||||
this.len = this.w * this.h;
|
||||
this.is_blocked = this.is_arrived = false;
|
||||
this.fPassable = typeof f_passable == "function" ? f_passable : function () {
|
||||
return true;
|
||||
};
|
||||
|
||||
this._init();
|
||||
};
|
||||
|
||||
TD.FindWay.prototype = {
|
||||
_init: function () {
|
||||
if (this.x1 == this.x2 && this.y1 == this.y2) {
|
||||
// 如果输入的坐标已经是终点了
|
||||
this.is_arrived = true;
|
||||
this.way = [
|
||||
[this.x1, this.y1]
|
||||
];
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.len; i++)
|
||||
this.m[i] = -2; // -2 表示未探索过,-1 表示不可到达
|
||||
|
||||
this.x = this.x1;
|
||||
this.y = this.y1;
|
||||
this.distance = 0;
|
||||
this.current = [
|
||||
[this.x, this.y]
|
||||
]; // 当前一步探索的格子
|
||||
|
||||
this.setVal(this.x, this.y, 0);
|
||||
|
||||
while (this.next()) {
|
||||
}
|
||||
},
|
||||
getVal: function (x, y) {
|
||||
var p = y * this.w + x;
|
||||
return p < this.len ? this.m[p] : -1;
|
||||
},
|
||||
setVal: function (x, y, v) {
|
||||
var p = y * this.w + x;
|
||||
if (p > this.len) return false;
|
||||
this.m[p] = v;
|
||||
},
|
||||
/**
|
||||
* 得到指定坐标的邻居,即从指定坐标出发,1 步之内可以到达的格子
|
||||
* 目前返回的是指定格子的上、下、左、右四个邻格
|
||||
* @param x {Number}
|
||||
* @param y {Number}
|
||||
*/
|
||||
getNeighborsOf: function (x, y) {
|
||||
var nbs = [];
|
||||
if (y > 0) nbs.push([x, y - 1]);
|
||||
if (x < this.w - 1) nbs.push([x + 1, y]);
|
||||
if (y < this.h - 1) nbs.push([x, y + 1]);
|
||||
if (x > 0) nbs.push([x - 1, y]);
|
||||
|
||||
return nbs;
|
||||
},
|
||||
/**
|
||||
* 取得当前一步可到达的 n 个格子的所有邻格
|
||||
*/
|
||||
getAllNeighbors: function () {
|
||||
var nbs = [], nb1, i, c, l = this.current.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
c = this.current[i];
|
||||
nb1 = this.getNeighborsOf(c[0], c[1]);
|
||||
nbs = nbs.concat(nb1);
|
||||
}
|
||||
return nbs;
|
||||
},
|
||||
/**
|
||||
* 从终点倒推,寻找从起点到终点最近的路径
|
||||
* 此处的实现是,从终点开始,从当前格子的邻格中寻找值最低(且大于 0)的格子,
|
||||
* 直到到达起点。
|
||||
* 这个实现需要反复地寻找邻格,有时邻格中有多个格子的值都为最低,这时就从中
|
||||
* 随机选取一个。还有一种实现方式是在一开始的遍历中,给每一个到达过的格子添加
|
||||
* 一个值,指向它的来时的格子(父格子)。
|
||||
*/
|
||||
findWay: function () {
|
||||
var x = this.x2,
|
||||
y = this.y2,
|
||||
nb, max_len = this.len,
|
||||
nbs_len,
|
||||
nbs, i, l, v, min_v = -1,
|
||||
closest_nbs;
|
||||
|
||||
while ((x != this.x1 || y != this.y1) && min_v != 0 &&
|
||||
this.way.length < max_len) {
|
||||
|
||||
this.way.unshift([x, y]);
|
||||
|
||||
nbs = this.getNeighborsOf(x, y);
|
||||
nbs_len = nbs.length;
|
||||
closest_nbs = [];
|
||||
|
||||
// 在邻格中寻找最小的 v
|
||||
min_v = -1;
|
||||
for (i = 0; i < nbs_len; i++) {
|
||||
v = this.getVal(nbs[i][0], nbs[i][1]);
|
||||
if (v < 0) continue;
|
||||
if (min_v < 0 || min_v > v)
|
||||
min_v = v;
|
||||
}
|
||||
// 找出所有 v 最小的邻格
|
||||
for (i = 0; i < nbs_len; i++) {
|
||||
nb = nbs[i];
|
||||
if (min_v == this.getVal(nb[0], nb[1])) {
|
||||
closest_nbs.push(nb);
|
||||
}
|
||||
}
|
||||
|
||||
// 从 v 最小的邻格中随机选取一个作为当前格子
|
||||
l = closest_nbs.length;
|
||||
i = l > 1 ? Math.floor(Math.random() * l) : 0;
|
||||
nb = closest_nbs[i];
|
||||
|
||||
x = nb[0];
|
||||
y = nb[1];
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 到达终点
|
||||
*/
|
||||
arrive: function () {
|
||||
this.current = [];
|
||||
this.is_arrived = true;
|
||||
|
||||
this.findWay();
|
||||
},
|
||||
/**
|
||||
* 道路被阻塞
|
||||
*/
|
||||
blocked: function () {
|
||||
this.current = [];
|
||||
this.is_blocked = true;
|
||||
},
|
||||
/**
|
||||
* 下一次迭代
|
||||
* @return {Boolean} 如果返回值为 true ,表示未到达终点,并且道路
|
||||
* 未被阻塞,可以继续迭代;否则表示不必继续迭代
|
||||
*/
|
||||
next: function () {
|
||||
var neighbors = this.getAllNeighbors(), nb,
|
||||
l = neighbors.length,
|
||||
valid_neighbors = [],
|
||||
x, y,
|
||||
i, v;
|
||||
|
||||
this.distance++;
|
||||
|
||||
for (i = 0; i < l; i++) {
|
||||
nb = neighbors[i];
|
||||
x = nb[0];
|
||||
y = nb[1];
|
||||
if (this.getVal(x, y) != -2) continue; // 当前格子已探索过
|
||||
//grid = this.map.getGrid(x, y);
|
||||
//if (!grid) continue;
|
||||
|
||||
if (this.fPassable(x, y)) {
|
||||
// 可通过
|
||||
|
||||
/**
|
||||
* 从起点到当前格子的耗费
|
||||
* 这儿只是简单地把从起点到当前格子需要走几步作为耗费
|
||||
* 比较复杂的情况下,可能还需要考虑不同的路面耗费也会不同,
|
||||
* 比如沼泽地的耗费比平地要多。不过现在的版本中路况没有这么复杂,
|
||||
* 先不考虑。
|
||||
*/
|
||||
v = this.distance;
|
||||
|
||||
valid_neighbors.push(nb);
|
||||
} else {
|
||||
// 不可通过或有建筑挡着
|
||||
v = -1;
|
||||
}
|
||||
|
||||
this.setVal(x, y, v);
|
||||
|
||||
if (x == this.x2 && y == this.y2) {
|
||||
this.arrive();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid_neighbors.length == 0) {
|
||||
this.blocked();
|
||||
return false
|
||||
}
|
||||
this.current = valid_neighbors;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}); // _TD.a.push end
|
||||
|
||||
|
||||
459
html5-tower-defense-master/src/js/td.js
Normal file
@@ -0,0 +1,459 @@
|
||||
/*
|
||||
* Copyright (c) 2011.
|
||||
*
|
||||
* Author: oldj <oldj.wu@gmail.com>
|
||||
* Blog: http://oldj.net/
|
||||
*
|
||||
*/
|
||||
|
||||
var _TD = {
|
||||
a: [],
|
||||
retina: window.devicePixelRatio || 1,
|
||||
init: function (td_board, is_debug) {
|
||||
delete this.init; // 一旦初始化运行,即删除这个入口引用,防止初始化方法被再次调用
|
||||
|
||||
var i, TD = {
|
||||
version: "0.1.17", // 版本命名规范参考:http://semver.org/
|
||||
is_debug: !!is_debug,
|
||||
is_paused: true,
|
||||
width: 16, // 横向多少个格子
|
||||
height: 16, // 纵向多少个格子
|
||||
show_monster_life: true, // 是否显示怪物的生命值
|
||||
fps: 0,
|
||||
exp_fps: 24, // 期望的 fps
|
||||
exp_fps_half: 12,
|
||||
exp_fps_quarter: 6,
|
||||
exp_fps_eighth: 4,
|
||||
stage_data: {},
|
||||
defaultSettings: function () {
|
||||
return {
|
||||
step_time: 36, // 每一次 step 循环之间相隔多少毫秒
|
||||
grid_size: 32 * _TD.retina, // px
|
||||
padding: 10 * _TD.retina, // px
|
||||
global_speed: 0.1 // 全局速度系数
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @param ob_board
|
||||
*/
|
||||
init: function (ob_board/*, ob_info*/) {
|
||||
this.obj_board = TD.lang.$e(ob_board);
|
||||
this.canvas = this.obj_board.getElementsByTagName("canvas")[0];
|
||||
//this.obj_info = TD.lang.$e(ob_info);
|
||||
if (!this.canvas.getContext) return; // 不支持 canvas
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
// 让 canvas 可聚焦以确保能接收键盘/鼠标交互
|
||||
try {
|
||||
this.canvas.tabIndex = 0;
|
||||
this.canvas.style.outline = 'none';
|
||||
} catch (e) {}
|
||||
|
||||
|
||||
this.monster_type_count = TD.getDefaultMonsterAttributes(); // 一共有多少种怪物
|
||||
this.iframe = 0; // 当前播放到第几帧了
|
||||
this.last_iframe_time = (new Date()).getTime();
|
||||
this.fps = 0;
|
||||
|
||||
this.start();
|
||||
},
|
||||
|
||||
/**
|
||||
* 开始游戏,或重新开始游戏
|
||||
*/
|
||||
start: function () {
|
||||
clearTimeout(this._st);
|
||||
TD.log("Start!");
|
||||
var _this = this;
|
||||
this._exp_fps_0 = this.exp_fps - 0.4; // 下限
|
||||
this._exp_fps_1 = this.exp_fps + 0.4; // 上限
|
||||
|
||||
this.mode = "normal"; // mode 分为 normail(普通模式)及 build(建造模式)两种
|
||||
this.eventManager.clear(); // 清除事件管理器中监听的事件
|
||||
this.lang.mix(this, this.defaultSettings());
|
||||
this.stage = new TD.Stage("stage-main", TD.getDefaultStageData("stage_main"));
|
||||
|
||||
this.canvas.setAttribute("width", this.stage.width);
|
||||
this.canvas.setAttribute("height", this.stage.height);
|
||||
this.canvas.style.width = (this.stage.width / _TD.retina) + "px";
|
||||
this.canvas.style.height = (this.stage.height / _TD.retina) + "px";
|
||||
|
||||
this.canvas.onmousemove = function (e) {
|
||||
var xy = _this.getEventXY.call(_this, e);
|
||||
_this.hover(xy[0], xy[1]);
|
||||
};
|
||||
this.canvas.onclick = function (e) {
|
||||
var xy = _this.getEventXY.call(_this, e);
|
||||
_this.click(xy[0], xy[1]);
|
||||
};
|
||||
|
||||
this.is_paused = false;
|
||||
this.stage.start();
|
||||
|
||||
// 记录游戏开始时间(用于计算时长并在结束时上报)
|
||||
this.startedAt = (new Date()).getTime();
|
||||
|
||||
this.step();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* 作弊方法
|
||||
* @param cheat_code
|
||||
*
|
||||
* 用例:
|
||||
* 1、增加 100 万金钱:javascript:_TD.cheat="money+";void(0);
|
||||
* 2、难度增倍:javascript:_TD.cheat="difficulty+";void(0);
|
||||
* 3、难度减半:javascript:_TD.cheat="difficulty-";void(0);
|
||||
* 4、生命值恢复:javascript:_TD.cheat="life+";void(0);
|
||||
* 5、生命值降为最低:javascript:_TD.cheat="life-";void(0);
|
||||
*/
|
||||
checkCheat: function (cheat_code) {
|
||||
switch (cheat_code) {
|
||||
case "money+":
|
||||
this.money += 1000000;
|
||||
this.log("cheat success!");
|
||||
break;
|
||||
case "life+":
|
||||
this.life = 100;
|
||||
this.log("cheat success!");
|
||||
break;
|
||||
case "life-":
|
||||
this.life = 1;
|
||||
this.log("cheat success!");
|
||||
break;
|
||||
case "difficulty+":
|
||||
this.difficulty *= 2;
|
||||
this.log("cheat success! difficulty = " + this.difficulty);
|
||||
break;
|
||||
case "difficulty-":
|
||||
this.difficulty /= 2;
|
||||
this.log("cheat success! difficulty = " + this.difficulty);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 主循环方法
|
||||
*/
|
||||
step: function () {
|
||||
|
||||
if (this.is_debug && _TD && _TD.cheat) {
|
||||
// 检查作弊代码
|
||||
this.checkCheat(_TD.cheat);
|
||||
_TD.cheat = "";
|
||||
}
|
||||
|
||||
if (this.is_paused) return;
|
||||
|
||||
this.iframe++; // 当前总第多少帧
|
||||
if (this.iframe % 50 == 0) {
|
||||
// 计算 fps
|
||||
var t = (new Date()).getTime(),
|
||||
step_time = this.step_time;
|
||||
this.fps = Math.round(500000 / (t - this.last_iframe_time)) / 10;
|
||||
this.last_iframe_time = t;
|
||||
|
||||
// 动态调整 step_time ,保证 fps 恒定为 24 左右
|
||||
if (this.fps < this._exp_fps_0 && step_time > 1) {
|
||||
step_time--;
|
||||
} else if (this.fps > this._exp_fps_1) {
|
||||
step_time++;
|
||||
}
|
||||
// if (step_time != this.step_time)
|
||||
// TD.log("FPS: " + this.fps + ", Step Time: " + step_time);
|
||||
this.step_time = step_time;
|
||||
}
|
||||
if (this.iframe % 2400 == 0) TD.gc(); // 每隔一段时间自动回收垃圾
|
||||
|
||||
this.stage.step();
|
||||
this.stage.render();
|
||||
|
||||
var _this = this;
|
||||
this._st = setTimeout(function () {
|
||||
_this.step();
|
||||
}, this.step_time);
|
||||
},
|
||||
|
||||
/**
|
||||
* 取得事件相对于 canvas 左上角的坐标
|
||||
* @param e
|
||||
*/
|
||||
getEventXY: function (e) {
|
||||
// Use bounding client rect to correctly map event coordinates to canvas,
|
||||
// this handles iframe offsets, CSS scaling, and scrolling more reliably.
|
||||
try {
|
||||
var rect = this.canvas.getBoundingClientRect();
|
||||
var x = (e.clientX - rect.left);
|
||||
var y = (e.clientY - rect.top);
|
||||
// map to canvas pixel coordinates in case canvas is scaled via CSS
|
||||
var scaleX = this.canvas.width / rect.width;
|
||||
var scaleY = this.canvas.height / rect.height;
|
||||
return [Math.round(x * scaleX), Math.round(y * scaleY)];
|
||||
} catch (err) {
|
||||
// fallback to old method if something goes wrong
|
||||
var wra = TD.lang.$e("wrapper"),
|
||||
x = e.clientX - wra.offsetLeft - this.canvas.offsetLeft + Math.max(document.documentElement.scrollLeft, document.body.scrollLeft),
|
||||
y = e.clientY - wra.offsetTop - this.canvas.offsetTop + Math.max(document.documentElement.scrollTop, document.body.scrollTop);
|
||||
return [x * _TD.retina, y * _TD.retina];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 鼠标移到指定位置事件
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
hover: function (x, y) {
|
||||
this.eventManager.hover(x, y);
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击事件
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
click: function (x, y) {
|
||||
this.eventManager.click(x, y);
|
||||
},
|
||||
|
||||
/**
|
||||
* 是否将 canvas 中的鼠标指针变为手的形状
|
||||
* @param v {Boolean}
|
||||
*/
|
||||
mouseHand: function (v) {
|
||||
this.canvas.style.cursor = v ? "pointer" : "default";
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示调试信息,只在 is_debug 为 true 的情况下有效
|
||||
* @param txt
|
||||
*/
|
||||
log: function (txt) {
|
||||
this.is_debug && window.console && console.log && console.log(txt);
|
||||
},
|
||||
|
||||
/**
|
||||
* 回收内存
|
||||
* 注意:CollectGarbage 只在 IE 下有效
|
||||
*/
|
||||
gc: function () {
|
||||
if (window.CollectGarbage) {
|
||||
CollectGarbage();
|
||||
setTimeout(CollectGarbage, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (i = 0; this.a[i]; i++) {
|
||||
// 依次执行添加到列表中的函数
|
||||
this.a[i](TD);
|
||||
}
|
||||
delete this.a;
|
||||
|
||||
TD.init(td_board);
|
||||
|
||||
// Expose a small API for parent page to save/load minimal game state.
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.TD = TD;
|
||||
window.__TD_getState = function () {
|
||||
try {
|
||||
var scene = TD.stage && TD.stage.current_act && TD.stage.current_act.current_scene;
|
||||
var map = (TD.stage && TD.stage.map) || (scene && scene.map) || TD.map;
|
||||
var buildings = [];
|
||||
if (map && map.buildings && map.buildings.length) {
|
||||
for (var i = 0; i < map.buildings.length; i++) {
|
||||
var b = map.buildings[i];
|
||||
if (!b || !b.grid) continue;
|
||||
buildings.push({ type: b.type, mx: b.grid.mx, my: b.grid.my, level: b.level, money: b.money });
|
||||
}
|
||||
}
|
||||
|
||||
// 添加怪物保存逻辑
|
||||
var monsters_data = [];
|
||||
if (scene && scene._step_elements) {
|
||||
// 遍历所有 step 元素层(共3层)
|
||||
for (var level = 0; level < scene._step_elements.length; level++) {
|
||||
var elements = scene._step_elements[level];
|
||||
for (var mi = 0; mi < elements.length; mi++) {
|
||||
var el = elements[mi];
|
||||
// 筛选有效的怪物
|
||||
if (el && el.is_monster && el.is_valid && el.grid) {
|
||||
var monster_data = {
|
||||
idx: el.idx,
|
||||
difficulty: el.difficulty,
|
||||
life: el.life,
|
||||
life0: el.life0,
|
||||
shield: el.shield,
|
||||
speed: el.speed,
|
||||
damage: el.damage,
|
||||
money: el.money,
|
||||
mx: el.grid.mx,
|
||||
my: el.grid.my,
|
||||
cx: el.cx,
|
||||
cy: el.cy,
|
||||
r: el.r,
|
||||
color: el.color,
|
||||
toward: el.toward,
|
||||
way: el.way || [],
|
||||
next_grid_mx: el.next_grid ? el.next_grid.mx : null,
|
||||
next_grid_my: el.next_grid ? el.next_grid.my : null,
|
||||
_dx: el._dx || 0,
|
||||
_dy: el._dy || 0,
|
||||
step_level: el.step_level,
|
||||
render_level: el.render_level,
|
||||
is_paused: el.is_paused,
|
||||
is_blocked: el.is_blocked
|
||||
};
|
||||
monsters_data.push(monster_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { money: TD.money, life: TD.life, score: TD.score, wave: (scene && scene.wave) || 0, buildings: buildings, monsters: monsters_data };
|
||||
} catch (e) {
|
||||
console.error('__TD_getState error', e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
window.__TD_loadState = function (s) {
|
||||
try {
|
||||
if (!s) return;
|
||||
var scene = TD.stage && TD.stage.current_act && TD.stage.current_act.current_scene;
|
||||
var map = (TD.stage && TD.stage.map) || (scene && scene.map) || TD.map;
|
||||
if (!map) return;
|
||||
|
||||
// remove existing buildings
|
||||
for (var i = 0; i < map.grids.length; i++) {
|
||||
var g = map.grids[i];
|
||||
if (g && g.building) g.removeBuilding();
|
||||
}
|
||||
|
||||
// 清除现有怪物(新增)
|
||||
if (scene && scene._step_elements) {
|
||||
for (var level = 0; level < scene._step_elements.length; level++) {
|
||||
var elements = scene._step_elements[level];
|
||||
for (var ei = elements.length - 1; ei >= 0; ei--) {
|
||||
var el = elements[ei];
|
||||
if (el && el.is_monster) {
|
||||
el.pause && el.pause();
|
||||
el.del && el.del();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (map.monsters) {
|
||||
map.monsters = [];
|
||||
}
|
||||
|
||||
// add saved buildings
|
||||
if (s.buildings && s.buildings.length) {
|
||||
for (var j = 0; j < s.buildings.length; j++) {
|
||||
var bi = s.buildings[j];
|
||||
var grid = map.getGrid(bi.mx, bi.my);
|
||||
if (grid) grid.addBuilding(bi.type);
|
||||
var b = grid && grid.building;
|
||||
if (b) {
|
||||
if (typeof bi.level !== 'undefined') b.level = bi.level;
|
||||
if (typeof bi.money !== 'undefined') b.money = bi.money;
|
||||
b.updateBtnDesc && b.updateBtnDesc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复保存的怪物(新增)
|
||||
if (s.monsters && s.monsters.length) {
|
||||
for (var mj = 0; mj < s.monsters.length; mj++) {
|
||||
var md = s.monsters[mj];
|
||||
|
||||
// 验证生命值
|
||||
if (md.life <= 0 || md.life > md.life0) {
|
||||
md.life = md.life0;
|
||||
}
|
||||
|
||||
var monster_grid = map.getGrid(md.mx, md.my);
|
||||
if (!monster_grid || monster_grid === map.exit) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建怪物实例
|
||||
var monster = new TD.Monster(null, {
|
||||
idx: md.idx,
|
||||
difficulty: md.difficulty,
|
||||
step_level: md.step_level,
|
||||
render_level: md.render_level
|
||||
});
|
||||
|
||||
// 手动设置保存的属性
|
||||
monster.life = md.life;
|
||||
monster.life0 = md.life0;
|
||||
monster.shield = md.shield;
|
||||
monster.speed = md.speed;
|
||||
monster.damage = md.damage;
|
||||
monster.money = md.money;
|
||||
monster.r = md.r;
|
||||
monster.color = md.color;
|
||||
monster.toward = md.toward;
|
||||
monster._dx = md._dx;
|
||||
monster._dy = md._dy;
|
||||
monster.is_paused = md.is_paused;
|
||||
monster.is_blocked = md.is_blocked;
|
||||
|
||||
// 设置位置
|
||||
monster.beAddToGrid(monster_grid);
|
||||
monster.cx = md.cx;
|
||||
monster.cy = md.cy;
|
||||
monster.caculatePos();
|
||||
|
||||
// 设置下一个目标格子
|
||||
if (md.next_grid_mx !== null && md.next_grid_my !== null) {
|
||||
monster.next_grid = map.getGrid(md.next_grid_mx, md.next_grid_my);
|
||||
}
|
||||
|
||||
// 设置路径
|
||||
var validWay = [];
|
||||
for (var wi = 0; wi < md.way.length; wi++) {
|
||||
var wp = md.way[wi];
|
||||
var wpgrid = map.getGrid(wp[0], wp[1]);
|
||||
if (wpgrid) validWay.push(wp);
|
||||
}
|
||||
monster.way = validWay;
|
||||
|
||||
// 检查路径并重新寻路
|
||||
if (validWay.length === 0 || !monster.next_grid || monster.is_blocked) {
|
||||
monster.findWay && monster.findWay();
|
||||
if (!monster.next_grid) monster.beBlocked && monster.beBlocked();
|
||||
}
|
||||
|
||||
// 添加到场景和地图
|
||||
monster_grid.scene.addElement(monster, monster.step_level, monster.render_level);
|
||||
map.monsters.push(monster);
|
||||
|
||||
// 启动怪物
|
||||
if (!monster.is_paused) monster.start && monster.start();
|
||||
} catch (monsterErr) {
|
||||
console.warn('__TD_loadState: Failed to restore monster:', monsterErr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof s.money !== 'undefined') TD.money = s.money;
|
||||
if (typeof s.life !== 'undefined') TD.life = s.life;
|
||||
if (typeof s.score !== 'undefined') TD.score = s.score;
|
||||
if (typeof s.wave !== 'undefined' && scene) scene.wave = s.wave;
|
||||
} catch (e) {
|
||||
console.error('__TD_loadState error', e);
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('expose TD api failed', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
348
html5-tower-defense-master/src/td.html
Normal file
@@ -0,0 +1,348 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=1.0, user-scalable=yes">
|
||||
<title>HTML5 Tower Defense</title>
|
||||
<link href="css/c.css" rel="stylesheet" media="screen">
|
||||
</head>
|
||||
|
||||
<body id="tower-defense">
|
||||
<div id="wrapper">
|
||||
<div id="td-wrapper">
|
||||
<h1>HTML5 塔防游戏</h1>
|
||||
|
||||
<div id="td-loading">加载中...</div>
|
||||
<div id="td-board">
|
||||
<canvas id="td-canvas">抱歉,您的浏览器不支持 HTML 5 Canvas 标签,请使用 IE9 / Chrome / Opera 等浏览器浏览本页以获得最佳效果。</canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="about">
|
||||
<a href="http://oldj.net/article/html5-tower-defense/" target="_blank">关于</a> |
|
||||
<a href="https://github.com/oldj/html5-tower-defense" target="_blank">源码</a> |
|
||||
<a href="http://oldj.net/">oldj.net</a> © 2010-2011
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="js/td.js"></script>
|
||||
<script type="text/javascript" src="js/td-lang.js"></script>
|
||||
<script type="text/javascript" src="js/td-event.js"></script>
|
||||
<script type="text/javascript" src="js/td-stage.js"></script>
|
||||
<script type="text/javascript" src="js/td-element.js"></script>
|
||||
<script type="text/javascript" src="js/td-obj-map.js"></script>
|
||||
<script type="text/javascript" src="js/td-obj-grid.js"></script>
|
||||
<script type="text/javascript" src="js/td-obj-building.js"></script>
|
||||
<script type="text/javascript" src="js/td-obj-monster.js"></script>
|
||||
<script type="text/javascript" src="js/td-obj-panel.js"></script>
|
||||
<script type="text/javascript" src="js/td-data-stage-1.js"></script>
|
||||
<script type="text/javascript" src="js/td-cfg-buildings.js"></script>
|
||||
<script type="text/javascript" src="js/td-cfg-monsters.js"></script>
|
||||
<script type="text/javascript" src="js/td-render-buildings.js"></script>
|
||||
<script type="text/javascript" src="js/td-msg-zh.js"></script>
|
||||
<script type="text/javascript" src="js/td-walk.js"></script>
|
||||
|
||||
<!-- Monster save/load enhancement -->
|
||||
<script type="text/javascript">
|
||||
// 延迟执行以确保 td.js 已加载
|
||||
setTimeout(function() {
|
||||
console.log('[MonsterSaveLoad] Applying monster save/load enhancement...');
|
||||
|
||||
// 保存原始函数
|
||||
var originalGetState = window.__TD_getState;
|
||||
var originalLoadState = window.__TD_loadState;
|
||||
|
||||
// 替换 __TD_getState 以支持怪物保存
|
||||
window.__TD_getState = function () {
|
||||
try {
|
||||
console.log('[__TD_getState] ===== START SAVING STATE =====');
|
||||
|
||||
var scene = TD.stage && TD.stage.current_act && TD.stage.current_act.current_scene;
|
||||
console.log('[__TD_getState] scene:', scene);
|
||||
console.log('[__TD_getState] scene.map:', scene ? scene.map : 'scene is null');
|
||||
|
||||
var map = scene && scene.map;
|
||||
console.log('[__TD_getState] map:', map);
|
||||
|
||||
var buildings = [];
|
||||
if (map && map.buildings && map.buildings.length) {
|
||||
console.log('[__TD_getState] Found ' + map.buildings.length + ' buildings');
|
||||
for (var i = 0; i < map.buildings.length; i++) {
|
||||
var b = map.buildings[i];
|
||||
if (!b || !b.grid) continue;
|
||||
buildings.push({ type: b.type, mx: b.grid.mx, my: b.grid.my, level: b.level, money: b.money });
|
||||
}
|
||||
}
|
||||
|
||||
// 保存怪物状态 - 从 map.monsters 获取
|
||||
var monsters_data = [];
|
||||
if (map && map.monsters && map.monsters.length) {
|
||||
console.log('[__TD_getState] Found ' + map.monsters.length + ' monsters in map.monsters');
|
||||
for (var i = 0; i < map.monsters.length; i++) {
|
||||
var m = map.monsters[i];
|
||||
console.log('[__TD_getState] Monster ' + i + ':', {
|
||||
'is_valid': m ? m.is_valid : 'null',
|
||||
'grid': m ? m.grid : 'null',
|
||||
'idx': m ? m.idx : 'null',
|
||||
'life': m ? m.life : 'null'
|
||||
});
|
||||
|
||||
if (!m || !m.is_valid || !m.grid) {
|
||||
console.log('[__TD_getState] Skipping invalid monster', m);
|
||||
continue;
|
||||
}
|
||||
|
||||
var monster_data = {
|
||||
// 核心属性
|
||||
idx: m.idx,
|
||||
difficulty: m.difficulty,
|
||||
life: m.life,
|
||||
life0: m.life0,
|
||||
shield: m.shield,
|
||||
speed: m.speed,
|
||||
damage: m.damage,
|
||||
money: m.money,
|
||||
|
||||
// 位置和渲染
|
||||
mx: m.grid.mx,
|
||||
my: m.grid.my,
|
||||
cx: m.cx,
|
||||
cy: m.cy,
|
||||
r: m.r,
|
||||
color: m.color,
|
||||
|
||||
// 路径和导航
|
||||
toward: m.toward,
|
||||
way: m.way || [],
|
||||
next_grid_mx: m.next_grid ? m.next_grid.mx : null,
|
||||
next_grid_my: m.next_grid ? m.next_grid.my : null,
|
||||
|
||||
// 移动辅助
|
||||
_dx: m._dx || 0,
|
||||
_dy: m._dy || 0,
|
||||
|
||||
// 配置级别
|
||||
step_level: m.step_level,
|
||||
render_level: m.render_level,
|
||||
|
||||
// 状态标志
|
||||
is_paused: m.is_paused,
|
||||
is_blocked: m.is_blocked
|
||||
};
|
||||
|
||||
monsters_data.push(monster_data);
|
||||
console.log('[__TD_getState] Saved monster data:', monster_data);
|
||||
}
|
||||
} else {
|
||||
console.log('[__TD_getState] No monsters found');
|
||||
console.log('[__TD_getState] map:', map);
|
||||
console.log('[__TD_getState] map.monsters:', map ? map.monsters : 'map is null');
|
||||
}
|
||||
|
||||
var state = {
|
||||
money: TD.money,
|
||||
life: TD.life,
|
||||
score: TD.score,
|
||||
wave: (scene && scene.wave) || 0,
|
||||
buildings: buildings,
|
||||
monsters: monsters_data
|
||||
};
|
||||
console.log('[__TD_getState] Returning state with ' + monsters_data.length + ' monsters');
|
||||
console.log('[__TD_getState] ===== END SAVING STATE =====');
|
||||
return state;
|
||||
} catch (e) {
|
||||
console.error('[__TD_getState] error', e);
|
||||
console.error('[__TD_getState] error stack', e.stack);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 替换 __TD_loadState 以支持怪物恢复
|
||||
window.__TD_loadState = function (s) {
|
||||
try {
|
||||
console.log('[__TD_loadState] ===== START LOADING STATE =====');
|
||||
console.log('[__TD_loadState] State received:', s);
|
||||
|
||||
if (!s) {
|
||||
console.warn('[__TD_loadState] State is null or undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
var scene = TD.stage && TD.stage.current_act && TD.stage.current_act.current_scene;
|
||||
console.log('[__TD_loadState] scene:', scene);
|
||||
|
||||
var map = scene && scene.map;
|
||||
if (!map) {
|
||||
console.warn('[__TD_loadState] Map is null');
|
||||
return;
|
||||
}
|
||||
console.log('[__TD_loadState] map:', map);
|
||||
console.log('[__TD_loadState] Current monsters count:', map.monsters ? map.monsters.length : 'null');
|
||||
|
||||
// remove existing buildings
|
||||
for (var i = 0; i < map.grids.length; i++) {
|
||||
var g = map.grids[i];
|
||||
if (g && g.building) g.removeBuilding();
|
||||
}
|
||||
console.log('[__TD_loadState] Removed existing buildings');
|
||||
|
||||
// remove existing monsters
|
||||
if (map.monsters && map.monsters.length) {
|
||||
console.log('[__TD_loadState] Removing ' + map.monsters.length + ' existing monsters');
|
||||
for (var mi = 0; mi < map.monsters.length; mi++) {
|
||||
var old_monster = map.monsters[mi];
|
||||
if (old_monster) {
|
||||
old_monster.pause();
|
||||
old_monster.del();
|
||||
}
|
||||
}
|
||||
map.monsters = [];
|
||||
console.log('[__TD_loadState] Cleared monsters array');
|
||||
}
|
||||
|
||||
// add saved buildings
|
||||
if (s.buildings && s.buildings.length) {
|
||||
console.log('[__TD_loadState] Restoring ' + s.buildings.length + ' buildings');
|
||||
for (var j = 0; j < s.buildings.length; j++) {
|
||||
var bi = s.buildings[j];
|
||||
var grid = map.getGrid(bi.mx, bi.my);
|
||||
if (grid) grid.addBuilding(bi.type);
|
||||
var b = grid && grid.building;
|
||||
if (b) {
|
||||
if (typeof bi.level !== 'undefined') b.level = bi.level;
|
||||
if (typeof bi.money !== 'undefined') b.money = bi.money;
|
||||
b.updateBtnDesc && b.updateBtnDesc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add saved monsters
|
||||
if (s.monsters && s.monsters.length) {
|
||||
console.log('[__TD_loadState] Found ' + s.monsters.length + ' monsters to restore');
|
||||
for (var mj = 0; mj < s.monsters.length; mj++) {
|
||||
var md = s.monsters[mj];
|
||||
console.log('[__TD_loadState] Processing monster:', md);
|
||||
|
||||
// 验证生命值
|
||||
if (md.life <= 0) {
|
||||
console.warn('[__TD_loadState] Cannot restore monster: life is 0', md);
|
||||
continue;
|
||||
}
|
||||
if (md.life > md.life0) {
|
||||
md.life = md.life0; // 修正:当前生命值不应超过初始值
|
||||
}
|
||||
|
||||
var monster_grid = map.getGrid(md.mx, md.my);
|
||||
if (!monster_grid) {
|
||||
console.warn('[__TD_loadState] Cannot restore monster: grid not found', md.mx, md.my);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查怪物是否在终点
|
||||
if (monster_grid === map.exit) {
|
||||
console.warn('[__TD_loadState] Skipping monster at exit', md);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log('[__TD_loadState] Creating monster instance');
|
||||
// 创建怪物实例
|
||||
var monster = new TD.Monster(null, {
|
||||
idx: md.idx,
|
||||
difficulty: md.difficulty,
|
||||
step_level: md.step_level,
|
||||
render_level: md.render_level
|
||||
});
|
||||
console.log('[__TD_loadState] Monster instance created');
|
||||
|
||||
// 手动设置保存的属性(跳过_init中的随机计算)
|
||||
monster.life = md.life;
|
||||
monster.life0 = md.life0;
|
||||
monster.shield = md.shield;
|
||||
monster.speed = md.speed;
|
||||
monster.damage = md.damage;
|
||||
monster.money = md.money;
|
||||
monster.r = md.r;
|
||||
monster.color = md.color;
|
||||
monster.toward = md.toward;
|
||||
monster._dx = md._dx;
|
||||
monster._dy = md._dy;
|
||||
monster.is_paused = md.is_paused;
|
||||
monster.is_blocked = md.is_blocked;
|
||||
console.log('[__TD_loadState] Monster properties set');
|
||||
|
||||
// 设置位置
|
||||
monster.beAddToGrid(monster_grid);
|
||||
monster.cx = md.cx;
|
||||
monster.cy = md.cy;
|
||||
monster.caculatePos();
|
||||
console.log('[__TD_loadState] Monster positioned at grid (' + md.mx + ',' + md.my + ') cx:' + monster.cx + ' cy:' + monster.cy);
|
||||
|
||||
// 设置下一个目标格子
|
||||
if (md.next_grid_mx !== null && md.next_grid_my !== null) {
|
||||
monster.next_grid = map.getGrid(md.next_grid_mx, md.next_grid_my);
|
||||
console.log('[__TD_loadState] Monster next_grid set to (' + md.next_grid_mx + ',' + md.next_grid_my + ')');
|
||||
}
|
||||
|
||||
// 验证并过滤路径点
|
||||
var valid_way = [];
|
||||
for (var wi = 0; wi < md.way.length; wi++) {
|
||||
var wp = md.way[wi];
|
||||
var wp_grid = map.getGrid(wp[0], wp[1]);
|
||||
if (wp_grid) {
|
||||
valid_way.push(wp);
|
||||
}
|
||||
}
|
||||
monster.way = valid_way;
|
||||
console.log('[__TD_loadState] Monster way set with ' + valid_way.length + ' waypoints');
|
||||
|
||||
// 如果路径被严重破坏,触发重新寻路
|
||||
if (valid_way.length === 0) {
|
||||
monster.findWay();
|
||||
console.log('[__TD_loadState] Monster found new path after way was empty');
|
||||
}
|
||||
|
||||
// 恢复后检查怪物是否被阻塞
|
||||
if (monster.is_blocked || !monster.next_grid) {
|
||||
monster.findWay();
|
||||
if (!monster.next_grid) {
|
||||
monster.beBlocked();
|
||||
}
|
||||
console.log('[__TD_loadState] Monster path checked, next_grid:', monster.next_grid);
|
||||
}
|
||||
|
||||
// 添加到场景和地图
|
||||
monster_grid.scene.addElement(monster, monster.step_level, monster.render_level);
|
||||
map.monsters.push(monster);
|
||||
console.log('[__TD_loadState] Monster added, total monsters:', map.monsters.length);
|
||||
|
||||
// 如果未被暂停,启动怪物
|
||||
if (!monster.is_paused) {
|
||||
monster.start();
|
||||
console.log('[__TD_loadState] Monster started');
|
||||
}
|
||||
}
|
||||
console.log('[__TD_loadState] Finished restoring monsters, final count:', map.monsters.length);
|
||||
} else {
|
||||
console.log('[__TD_loadState] No monsters to restore');
|
||||
}
|
||||
|
||||
if (typeof s.money !== 'undefined') TD.money = s.money;
|
||||
if (typeof s.life !== 'undefined') TD.life = s.life;
|
||||
if (typeof s.score !== 'undefined') TD.score = s.score;
|
||||
if (typeof s.wave !== 'undefined' && scene) scene.wave = s.wave;
|
||||
|
||||
console.log('[__TD_loadState] Load completed. Money:' + TD.money + ' Life:' + TD.life + ' Score:' + TD.score);
|
||||
console.log('[__TD_loadState] ===== END LOADING STATE =====');
|
||||
} catch (e) {
|
||||
console.error('[__TD_loadState] error', e);
|
||||
console.error('[__TD_loadState] error stack', e.stack);
|
||||
}
|
||||
};
|
||||
|
||||
console.log('[MonsterSaveLoad] Enhancement applied successfully!');
|
||||
}, 500);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
69
html5-tower-defense-master/tools/compressor.py
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/python2.6
|
||||
# @author: allenm, oldj
|
||||
#
|
||||
# @link: https://github.com/allenm/js-css-compressor
|
||||
# @link: https://github.com/oldj/js-css-compressor
|
||||
#
|
||||
|
||||
import httplib
|
||||
import urllib
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Define the parameters for the POST request and encode them in
|
||||
# a URL-safe format.
|
||||
|
||||
|
||||
def compressor(savename, filenames):
|
||||
''' compressor and combine the javascript files. This script use the google closure REST API '''
|
||||
|
||||
filenames = (v.strip() for v in filenames.split(";"))
|
||||
code = []
|
||||
for fn in filenames:
|
||||
if fn.startswith('http://'):
|
||||
# url
|
||||
code.append(('code_url', fn))
|
||||
else:
|
||||
# local file
|
||||
if not os.path.isfile(fn):
|
||||
print 'ERROR: "%s" is not a valid file!' % fn
|
||||
return False
|
||||
code.append(('js_code', open(fn).read()))
|
||||
|
||||
code.extend([
|
||||
('compilation_level', 'SIMPLE_OPTIMIZATIONS'),
|
||||
('output_format', 'text'),
|
||||
('output_info', 'compiled_code'),
|
||||
])
|
||||
|
||||
params = urllib.urlencode(code)
|
||||
|
||||
# Always use the following value for the Content-type header.
|
||||
headers = {'Content-type': 'application/x-www-form-urlencoded'}
|
||||
conn = httplib.HTTPConnection('closure-compiler.appspot.com')
|
||||
conn.request('POST', '/compile', params, headers)
|
||||
response = conn.getresponse()
|
||||
data = response.read()
|
||||
print 'DATA:'
|
||||
print '-' * 50
|
||||
print data.rstrip()
|
||||
conn.close()
|
||||
|
||||
donefile = open(savename, 'w')
|
||||
donefile.write(data)
|
||||
donefile.close()
|
||||
|
||||
print '-' * 50
|
||||
print '>> out: %s (%.2fK)' % (savename, len(data) / 1024.0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
if sys.argv.__len__() >= 3:
|
||||
compressor(sys.argv[1], sys.argv[2])
|
||||
else:
|
||||
print '''This script must contain at least two parameters.
|
||||
The first one is the filename which you want store the data after compress,
|
||||
the second is the urls or filenames of javascript file which you want compress,
|
||||
if you have more than one file to compress,
|
||||
use ";" to partition them.'''
|
||||
14
html5-tower-defense-master/tools/countjslines.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# 统计指定文件夹下的 js 一共有多少行
|
||||
|
||||
import os
|
||||
from glob import glob
|
||||
|
||||
src_folder = "../src/js"
|
||||
|
||||
if __name__ == "__main__":
|
||||
print "lines: %d" % sum(len(open(fn).readlines()) for \
|
||||
fn in glob(os.path.join(src_folder.replace("/", os.sep), "*.js")))
|
||||
raw_input("")
|
||||
|
||||
79
html5-tower-defense-master/tools/deploy.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
u"""
|
||||
发布脚本
|
||||
|
||||
此脚本把 src 目录下的 js 合并成一个,压缩,放到 build 目录下,
|
||||
再更新 build/td.html 里 .js 文件的时间戳以防止浏览器缓存。
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
||||
from compressor import compressor
|
||||
|
||||
|
||||
def updateHTML():
|
||||
u"更新 td.html 中 js 文件的时间戳,以防止最终访问页面时的缓存"
|
||||
|
||||
tdh = "../build/td.html"
|
||||
tdh = tdh.replace("/", os.sep)
|
||||
html = open(tdh).read()
|
||||
html = re.sub(r"\.js\?fmts=[\d\.]+", ".js?fmts=%.1f" % time.time(), html)
|
||||
open(tdh, "w").write(html)
|
||||
|
||||
|
||||
def compress(fn):
|
||||
u"压缩合并后的文件,需要网络支持"
|
||||
|
||||
print "compressing..."
|
||||
path, filename = os.path.split(fn)
|
||||
compressed_fn = os.path.join(path, filename.replace("-pkg.js", "-pkg-min.js"))
|
||||
compressor(compressed_fn, fn)
|
||||
print "compressed!"
|
||||
|
||||
|
||||
def merge():
|
||||
u"合并文件"
|
||||
|
||||
src_folder = "../src/js"
|
||||
files = [
|
||||
"td.js", "td-lang.js", "td-event.js",
|
||||
"td-stage.js", "td-element.js",
|
||||
"td-obj-map.js", "td-obj-grid.js",
|
||||
"td-obj-building.js", "td-obj-monster.js",
|
||||
"td-obj-panel.js", "td-data-stage-1.js",
|
||||
"td-cfg-buildings.js", "td-cfg-monsters.js",
|
||||
"td-render-buildings.js", "td-msg.js",
|
||||
"td-walk.js",
|
||||
]
|
||||
build_folder = "../build"
|
||||
build_name = "td-pkg.js"
|
||||
|
||||
print "merging..."
|
||||
src_folder = src_folder.replace("/", os.sep)
|
||||
build_folder = build_folder.replace("/", os.sep)
|
||||
c = "/** %s */" % build_name
|
||||
|
||||
for fn in files:
|
||||
fn = os.path.join(src_folder, fn)
|
||||
if os.path.isfile(fn):
|
||||
c = "%s\n\n%s" % (c, open(fn).read())
|
||||
else:
|
||||
print "ERROR: '%s' is not a file!" % fn
|
||||
return
|
||||
|
||||
build_path = os.path.join(build_folder, build_name)
|
||||
print "save to '%s'" % build_path
|
||||
open(build_path, "w").write(c)
|
||||
|
||||
print "merged!"
|
||||
|
||||
return build_path
|
||||
|
||||
if __name__ == "__main__":
|
||||
fn = merge()
|
||||
compress(fn)
|
||||
updateHTML()
|
||||
print "done!"
|
||||