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

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>