Migrate project from typing_practiceweb

This commit is contained in:
2026-05-14 16:04:14 +08:00
parent dbc5425706
commit 0e87d5546d
225 changed files with 92811 additions and 0 deletions

7
html5-tower-defense-master/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
.idea
*.iml
*.pyc
build/td-pkg.js
src/css/c-min.css
node_modules
npm-debug.log

View 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.

View 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 开始编写这个游戏。
## 开发计划
- 添加新武器“加农炮”,特性:击中怪物时会发生爆炸,造成面攻击。
- 添加关卡编辑器。
- 添加保存进度的功能。

View 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"
]);
});

File diff suppressed because it is too large Load Diff

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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);
},
/**
* 取得朝向
* 即下一个格子在当前格子的哪边
* 0123
*/
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

View 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

View 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

View 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);
}

View 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);
}
};

View 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

View 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

View 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);
}
}
};

View 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> &copy; 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>

View 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.'''

View 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("")

View 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!"