【默默努力】h5-game-blockBreaker
先放下游戏的效果,我不太会玩游戏
然后放下无私开源的作者大大的地址:https://github.com/yangyunhe369/h5-game-blockBreaker
这个游戏的话,我觉得应该是如果如果球跟砖碰到了,那么这个砖就消失,然后得一分,然后这个球就会以竖直的相同的角度返回,
如果球到了发射台,就会以在发射台的角度返回去,如果球没有碰到发射台,那么球就沿着坠落的方向消失,游戏结束。
接下来我们看代码
如果我写的话,应该分数是一个,官卡是一个,砖是一个幕布,下面的发射台是一个,球是一个。应该是5个,然后再加一个功能的js,6个js去实现这个。
我们来看看作者大大怎么实现的吧~
入口的文件html,里面引用了common.js,scene.js 还有game.js,canvas绘制了游戏的背景大小
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>打砖块v1.1</title>
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<canvas id="canvas" width="1000" height="500"></canvas>
<div>使用左右方向键,进行移动;空格键发射小球并开始游戏,游戏结束时按空格键重置游戏;P 键暂停游戏;通关游戏后,按 N 键可进入下一关卡</div>
<script src="js/common.js"></script>
<script src="js/scene.js"></script>
<script src="js/game.js"></script>
<script src="js/main.js"></script>
</body>
</html>
common.js中的代码写的真好,都看的懂,而且作者大大有注释,真的非常注重浏览器兼容性。
//common.js
/* by:弦云孤赫——David Yang
** github - https://github.com/yangyunhe369
*/
// 封装打印日志方法
const log = console.log.bind(console)
// 生成图片对象方法
const imageFromPath = function (src) {
let img = new Image()
img.src = './images/' + src
return img
}
// 检测页面不可见时自动暂停游戏方法
const isPageHidden = function (game) {
let hiddenProperty = 'hidden' in document ? 'hidden' :
'webkitHidden' in document ? 'webkitHidden' :
'mozHidden' in document ? 'mozHidden' :
null
let visibilityChangeEvent = hiddenProperty.replace(/hidden/i, 'visibilitychange')
// 监听页面是否可见事件
document.addEventListener(visibilityChangeEvent, function () {
if (!document[hiddenProperty]) { // 可见状态
setTimeout(function () {
game.state = game.state_RUNNING
}, 100)
} else { // 不可见状态
game.state = game.state_STOP
}
})
}
// 图片素材路径
const allImg = {
background: 'background.jpg',
paddle: 'paddle.png',
ball: 'ball.png',
block1: 'block001.png',
block2: 'block002.png',
}
main.js中就是游戏的初始化
//main.js
/* by:弦云孤赫——David Yang
** github - https://github.com/yangyunhe369
*/
// 游戏主函数
let _main = {
LV: 1, // 初始关卡
MAXLV: 2, // 最终关卡
scene: null, // 场景对象
blockList: null, // 所有砖块对象集合
ball: null, // 小球对象
paddle: null, // 挡板对象
score: null, // 计分板对象
ball_x: 491, // 小球默认x轴坐标
ball_y: 432, // 小球默认y轴坐标
paddle_x: 449, // 挡板默认x轴坐标
paddle_y: 450, // 挡板默认y轴坐标
score_x: 10, // 计分板默认x轴坐标
score_y: 30, // 计分板默认y轴坐标
fps: 60, // 游戏运行帧数
game: null, // 游戏主要逻辑对象
start: function () { // 游戏启动函数
let self = this
/**
* 生成场景(根据游戏难度级别不同,生成不同关卡)
*/
self.scene = new Scene(self.LV)
// 实例化所有砖块对象集合
self.blockList = self.scene.initBlockList()
/**
* 小球
*/
self.ball = new Ball(self)
/**
* 挡板
*/
self.paddle = new Paddle(self)
/**
* 计分板
*/
self.score = new Score(self)
/**
* 游戏主要逻辑
*/
self.game = new Game(self)
/**
* 游戏初始化
*/
self.game.init(self)
}
}
_main.start()
scene.js里面定义的是各种对象及方法
/* by:弦云孤赫——David Yang
** github - https://github.com/yangyunhe369
*/
// 定义挡板对象
class Paddle {
constructor (_main) {
let p = {
x: _main.paddle_x, // x轴坐标
y: _main.paddle_y, // y轴坐标
w: 102, // 图片宽度
h: 22, // 图片高度
speed: 10, // x轴移动速度
ballSpeedMax: 8, // 小球反弹速度最大值
image: imageFromPath(allImg.paddle), // 引入图片对象
isLeftMove: true, // 能否左移
isRightMove: true, // 能否右移
}
Object.assign(this, p)
}
moveLeft () {
this.x -= this.speed
}
moveRight () {
this.x += this.speed
}
// 小球、挡板碰撞检测
collide (ball) {
let b = ball
let p = this
if (Math.abs((b.x + b.w/2) - (p.x + p.w/2)) < (b.w + p.w)/2 &&
Math.abs((b.y + b.h/2) - (p.y + p.h/2)) < (b.h + p.h)/2) {
return true
}
return false
}
// 计算小球、挡板碰撞后x轴速度值
collideRange (ball) {
let b = ball
let p = this
let rangeX = 0
rangeX = (p.x + p.w/2) - (b.x + b.w/2)
if (rangeX < 0) { // 小球撞击挡板左侧
return rangeX / (b.w/2 + p.w/2) * p.ballSpeedMax
} else if (rangeX > 0) { // 小球撞击挡板右侧
return rangeX / (b.w/2 + p.w/2) * p.ballSpeedMax
}
}
}
// 小球对象
class Ball {
constructor (_main) {
let b = {
x: _main.ball_x, // x轴坐标
y: _main.ball_y, // y轴坐标
w: 18, // 图片宽度
h: 18, // 图片高度
speedX: 1, // x轴速度
speedY: 5, // y轴速度
image: imageFromPath(allImg.ball), // 图片对象
fired: false, // 是否运动,默认静止不动
}
Object.assign(this, b)
}
move (game) {
if (this.fired) {
// 碰撞边界检测
if (this.x < 0 || this.x > 1000 - this.w) {
this.speedX *= -1
}
if (this.y < 0) {
this.speedY *= -1
}
if (this.y > 500 - this.h) {
// 游戏结束
game.state = game.state_GAMEOVER
// game.isGameOver = true
}
// 移动
this.x -= this.speedX
this.y -= this.speedY
}
}
}
// 砖块
class Block {
constructor (x, y, life = 1) {
let bk = {
x: x, // x轴坐标
y: y, // y轴坐标
w: 50, // 图片宽度
h: 20, // 图片高度
image: life == 1 ? imageFromPath(allImg.block1) : imageFromPath(allImg.block2), // 图片对象
life: life, // 生命值
alive: true, // 是否存活
}
Object.assign(this, bk)
}
// 消除砖块
kill () {
this.life--
if (this.life == 0) {
this.alive = false
} else if (this.life == 1) {
this.image = imageFromPath(allImg.block1)
}
}
// 小球、砖块碰撞检测
collide (ball) {
let b = ball
if (Math.abs((b.x + b.w/2) - (this.x + this.w/2)) < (b.w + this.w)/2 &&
Math.abs((b.y + b.h/2) - (this.y + this.h/2)) < (b.h + this.h)/2) {
this.kill()
return true
} else {
return false
}
}
// 计算小球、砖块碰撞后x轴速度方向
collideBlockHorn (ball) {
let b = ball // 小球
let bk = this // 砖块
let rangeX = 0
let rangeY = 0
rangeX = Math.abs((b.x + b.w/2) - (bk.x + bk.w/2))
rangeY = Math.abs((b.y + b.h/2) - (bk.y + bk.h/2))
if (rangeX > bk.w/2 && rangeX < (bk.w/2 + b.w/2) && rangeY < (bk.h/2 + b.h/2)) { // X轴方向与砖块四角相交
if (b.x < bk.x && b.speedX > 0 || b.x > bk.x && b.speedX < 0) { // 小球在砖块左侧时
return false
} else { // 小球在砖块右侧
return true
}
}
return false
}
}
// 计分板
class Score {
constructor (_main) {
let s = {
x: _main.score_x, // x轴坐标
y: _main.score_y, // y轴坐标
text: '分数:', // 文本分数
textLv: '关卡:', // 关卡文本
score: 200, // 每个砖块对应分数
allScore: 0, // 总分
blockList: _main.blockList, // 砖块对象集合
blockListLen: _main.blockList.length, // 砖块总数量
lv: _main.LV, // 当前关卡
}
Object.assign(this, s)
}
// 计算总分
computeScore () {
let num = 0
let allNum = this.blockListLen
num = this.blockListLen - this.blockList.length
this.allScore = this.score * num
}
}
// 定义场景
class Scene {
constructor (lv) {
let s = {
lv: lv, // 游戏难度级别
canvas: document.getElementById("canvas"), // canvas对象
blockList: [], // 砖块坐标集合
}
Object.assign(this, s)
}
// 实例化所有砖块对象
initBlockList () {
this.creatBlockList()
let arr = []
for (let item of this.blockList) {
for (let list of item) {
if (list.type === 1) {
let obj = new Block(list.x, list.y)
arr.push(obj)
} else if (list.type === 2) {
let obj = new Block(list.x, list.y, 2)
arr.push(obj)
}
}
}
return arr
}
// 创建砖块坐标二维数组,并生成不同关卡
creatBlockList () {
let lv = this.lv, // 游戏难度级别
c_w = this.canvas.width, // canvas宽度
c_h = this.canvas.height, // canvas高度
xNum_max = c_w/50, // x轴砖块最大数量
yNum_max = 12, // y轴砖块最大数量
x_start = 0, // x轴起始坐标,根据砖块数量浮动
y_start = 60 // y轴起始坐标,默认从60起
switch (lv) {
case 1 : // 正三角形
var xNum = 16, // x轴砖块第一层数量
yNum = 9 // y轴砖块层数
// 循环y轴
for(let i = 0;i < yNum;i++){
let arr = []
// 修改每层x轴砖块数量
if (i === 0) {
xNum = 1
} else if (i === 1) {
xNum = 2
} else {
xNum += 2
}
x_start = (xNum_max - xNum)/2 * 50 // 修改每层x轴砖块起始坐标
// 循环x轴
for(let k = 0;k < xNum;k++){
if (i < 3) { // 前三排为特殊砖块
arr.push({
x: x_start + k*50,
y: y_start + i*20,
type: 2,
})
} else {
arr.push({
x: x_start + k*50,
y: y_start + i*20,
type: 1,
})
}
}
this.blockList.push(arr)
}
break
case 2 : // 倒三角形
var xNum = 16, // x轴砖块第一层数量
yNum = 9 // y轴砖块层数
// 循环y轴
for(let i = 0;i < yNum;i++){
let arr = []
// 修改每层x轴砖块数量
if (i === yNum - 1) {
xNum = 1
} else if (i === 0) {
xNum = xNum
} else {
xNum -= 2
}
x_start = (xNum_max - xNum)/2 * 50 // 修改每层x轴砖块起始坐标
// 循环x轴
for(let k = 0;k < xNum;k++){
if (i < 3) { // 前三排为特殊砖块
arr.push({
x: x_start + k*50,
y: y_start + i*20,
type: 2,
})
} else {
arr.push({
x: x_start + k*50,
y: y_start + i*20,
type: 1,
})
}
}
this.blockList.push(arr)
}
break
case 3 : // 工字形
var xNum = 16, // x轴砖块第一层数量
yNum = 9 // y轴砖块层数
// 循环y轴
for(let i = 0;i < yNum;i++){
let arr = []
// 修改每层x轴砖块数量
if (i === 0) {
xNum = xNum
} else if (i > 4) {
xNum += 2
} else {
xNum -= 2
}
x_start = (xNum_max - xNum)/2 * 50 // 修改每层x轴砖块起始坐标
// 循环x轴
for(let k = 0;k < xNum;k++){
if (i < 3) { // 前三排为特殊砖块
arr.push({
x: x_start + k*50,
y: y_start + i*20,
type: 2,
})
} else {
arr.push({
x: x_start + k*50,
y: y_start + i*20,
type: 1,
})
}
}
this.blockList.push(arr)
}
break
}
}
}
game.js中进行的是一些游戏初始化,以及必备说明
/* by:弦云孤赫——David Yang
** github - https://github.com/yangyunhe369
*/
// 游戏主要运行逻辑
class Game {
constructor (main) {
let g = {
main: main, // 游戏主函数
actions: {}, // 记录按键动作
keydowns: {}, // 记录按键keycode
state: 1, // 游戏状态值,初始默认为1
state_START: 1, // 开始游戏
state_RUNNING: 2, // 游戏开始运行
state_STOP: 3, // 暂停游戏
state_GAMEOVER: 4, // 游戏结束
state_UPDATE: 5, // 游戏通关
canvas: document.getElementById("canvas"), // canvas元素
context: document.getElementById("canvas").getContext("2d"), // canvas画布
timer: null, // 轮询定时器
fps: main.fps, // 动画帧数,默认60
}
Object.assign(this, g)
}
// 绘制页面所有素材
draw (paddle, ball, blockList, score) {
let g = this
// 清除画布
g.context.clearRect(0, 0, g.canvas.width, g.canvas.height)
// 绘制背景图
g.drawBg()
// 绘制挡板
g.drawImage(paddle)
// 绘制小球
g.drawImage(ball)
// 绘制砖块
g.drawBlocks(blockList)
// 绘制分数
g.drawText(score)
}
// 绘制图片
drawImage (obj) {
this.context.drawImage(obj.image, obj.x, obj.y)
}
// 绘制背景图
drawBg () {
let bg = imageFromPath(allImg.background)
this.context.drawImage(bg, 0, 0)
}
// 绘制所有砖块
drawBlocks (list) {
for (let item of list) {
this.drawImage(item)
}
}
// 绘制计数板
drawText (obj) {
this.context.font = '24px Microsoft YaHei'
this.context.fillStyle = '#fff'
// 绘制分数
this.context.fillText(obj.text + obj.allScore, obj.x, obj.y)
// 绘制关卡
this.context.fillText(obj.textLv + obj.lv, this.canvas.width - 100, obj.y)
}
// 游戏结束
gameOver () {
// 清除定时器
clearInterval(this.timer)
// 清除画布
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
// 绘制背景图
this.drawBg()
// 绘制提示文字
this.context.font = '48px Microsoft YaHei'
this.context.fillStyle = '#fff'
this.context.fillText('游戏结束', 404, 226)
}
// 游戏晋级
goodGame () {
// 清除定时器
clearInterval(this.timer)
// 清除画布
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
// 绘制背景图
this.drawBg()
// 绘制提示文字
this.context.font = '48px Microsoft YaHei'
this.context.fillStyle = '#fff'
this.context.fillText('恭喜晋级下一关卡', 308, 226)
}
// 游戏通关
finalGame () {
// 清除定时器
clearInterval(this.timer)
// 清除画布
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
// 绘制背景图
this.drawBg()
// 绘制提示文字
this.context.font = '48px Microsoft YaHei'
this.context.fillStyle = '#fff'
this.context.fillText('恭喜通关全部关卡', 308, 226)
}
// 注册事件
registerAction (key, callback) {
this.actions[key] = callback
}
// 小球碰撞砖块检测
checkBallBlock (g, paddle, ball, blockList, score) {
let p = paddle, b = ball
// 小球碰撞挡板检测
if (p.collide(b)) {
// 当小球运动方向趋向挡板中心时,Y轴速度取反,反之则不变
if (Math.abs(b.y + b.h/2 - p.y + p.h/2) > Math.abs(b.y + b.h/2 + b.speedY - p.y + p.h/2)) {
b.speedY *= -1
} else {
b.speedY *= 1
}
// 设置X轴速度
b.speedX = p.collideRange(b)
}
// 小球碰撞砖块检测
blockList.forEach(function (item, i, arr) {
if (item.collide(b)) { // 小球、砖块已碰撞
if (!item.alive) { // 砖块血量为0时,进行移除
arr.splice(i, 1)
}
// 当小球运动方向趋向砖块中心时,速度取反,反之则不变
if ((b.y < item.y && b.speedY < 0) || (b.y > item.y && b.speedY > 0)) {
if (!item.collideBlockHorn(b)) {
b.speedY *= -1
} else { // 当小球撞击砖块四角时,Y轴速度不变
b.speedY *= 1
}
} else {
b.speedY *= 1
}
// 当小球撞击砖块四角时,X轴速度取反
if (item.collideBlockHorn(b)) {
b.speedX *= -1
}
// 计算分数
score.computeScore()
}
})
// 挡板移动时边界检测
if (p.x <= 0) { // 到左边界时
p.isLeftMove = false
} else {
p.isLeftMove = true
}
if (p.x >= 1000 - p.w) { // 到右边界时
p.isRightMove = false
} else {
p.isRightMove = true
}
// 移动小球
b.move(g)
}
// 设置逐帧动画
setTimer (paddle, ball, blockList, score) {
let g = this
let main = g.main
g.timer = setInterval(function () {
// actions集合
let actions = Object.keys(g.actions)
for (let i = 0; i < actions.length; i++) {
let key = actions[i]
if(g.keydowns[key]) {
// 如果按键被按下,调用注册的action
g.actions[key]()
}
}
// 当砖块数量为0时,挑战成功
if (blockList.length == 0) {
if (main.LV === main.MAXLV) { // 最后一关通关
// 升级通关
g.state = g.state_UPDATE
// 挑战成功,渲染通关场景
g.finalGame()
} else { // 其余关卡通关
// 升级通关
g.state = g.state_UPDATE
// 挑战成功,渲染下一关卡场景
g.goodGame()
}
}
// 判断游戏是否结束
if (g.state === g.state_GAMEOVER) {
g.gameOver()
}
// 判断游戏开始时执行事件
if (g.state === g.state_RUNNING) {
g.checkBallBlock(g, paddle, ball, blockList, score)
// 绘制游戏所有素材
g.draw(paddle, ball, blockList, score)
} else if (g.state === g.state_START){
// 绘制游戏所有素材
g.draw(paddle, ball, blockList, score)
}
}, 1000/g.fps)
}
/**
* 初始化函数
*/
init () {
let g = this,
paddle = g.main.paddle,
ball = g.main.ball,
blockList = g.main.blockList,
score = g.main.score
// 设置键盘按下及松开相关注册函数
window.addEventListener('keydown', function (event) {
g.keydowns[event.keyCode] = true
})
window.addEventListener('keyup', function (event) {
g.keydowns[event.keyCode] = false
})
g.registerAction = function (key, callback) {
g.actions[key] = callback
}
// 注册左方向键移动事件
g.registerAction('37', function(){
// 判断游戏是否处于运行阶段
if (g.state === g.state_RUNNING && paddle.isLeftMove) {
paddle.moveLeft()
}
})
// 注册右方向键移动事件
g.registerAction('39', function(){
// 判断游戏是否处于运行阶段
if (g.state === g.state_RUNNING && paddle.isRightMove) {
paddle.moveRight()
}
})
window.addEventListener('keydown', function (event) {
switch (event.keyCode) {
// 注册空格键发射事件
case 32 :
if (g.state === g.state_GAMEOVER) { // 游戏结束时
// 开始游戏
g.state = g.state_START
// 初始化
g.main.start()
} else {
// 开始游戏
ball.fired = true
g.state = g.state_RUNNING
}
break
// N 键进入下一关卡
case 78 :
// 游戏状态为通关,且不为最终关卡时
if (g.state === g.state_UPDATE && g.main.LV !== g.main.MAXLV) { // 进入下一关
// 开始游戏
g.state = g.state_START
// 初始化下一关卡
g.main.start(++g.main.LV)
}
break
// P 键暂停游戏事件
case 80 :
g.state = g.state_STOP
break
}
})
// 设置轮询定时器
g.setTimer(paddle, ball, blockList, score)
}
}
后记:很多代码没有看懂,珍惜时间,好好学习哇~
【默默努力】h5-game-blockBreaker的更多相关文章
- 2018.5.2(7:20到的办公室开始早课 阮一峰的JS) 所有的默默努力都是为了让自己看起来毫不费力
continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环. break语句用于跳出代码块或循环. 标签(label) JavaScript 语言允许,语句的前面有标签(label) ...
- 【默默努力】PixelFire
先放下我玩游戏的效果图: 关于游戏最后的结束部分其实我还没有截图,看着挺好看的,后面的效果 再放作者大大的项目地址:https://github.com/panruiplay/PixelFire 接下 ...
- 【默默努力】fishingGame
这个捕鱼游戏挺有意思的,通过发射子弹,打鱼.打鱼的子弹会消耗金币,但是打鱼如果打到了鱼,就会奖励金币的数量. 我如果写这个的话,应该会画一个 背景海底,然后生成很多鱼的图片,还要有一个大炮,金币.大炮 ...
- 【默默努力】h5-game-heroVSmonster
先放下作者大大的项目地址:https://github.com/yangyunhe369/h5-game-heroVSmonster 然后游戏的效果为 截动图的按键与游戏按键应该冲突,我就截几张图片了 ...
- 【默默努力】ig-wxz-and-hotdog
这个是一个非常跟热点的小游戏,思聪吃热狗.这个游戏的话,我感觉思路还挺简单的,天上会掉热狗和障碍物, 思聪在下面张开嘴巴,进行左右移动,接热狗.如果吃到的是热狗就得一分,如果思聪吃到的不是热狗,是障碍 ...
- 【默默努力】react-drag-grid
先放项目地址:https://github.com/Bilif/react-drag-grid 项目运行效果 感谢无私开源的程序员 先看项目入口文件 //index.js import React f ...
- 【默默努力】vue-pc-app
最近在github上面看到了一个团队的项目,真的非常赞.他们进行vue-cli的二次开发,将项目用自己的方式打包. 今天的这个开源项目地址为:https://github.com/tffe-team/ ...
- Shader的学习方法总结
最近网友candycat1992的新书<Unity Shader入门精要>出版了,估计万千的中国unity开发者又要掀起一波学Shader热潮了.我也想把自己这几年学习Shader的一些历 ...
- VIM移动
VIM移动 断断续续的使用VIM也一年了,会的始终都是那么几个命令,效率极低 前几个星期把Windows换成了Linux Mint,基本上也稳定了下来 就今晚,我已经下定决心开始新的VIM之旅,顺 ...
随机推荐
- hdu多校第六场1005 (hdu6638) Snowy Smilel 线段树/区间最大和
题意: 给定一个矩阵,矩阵上有若干点,每个点有正或负的权值,找一个方框框住一些点使得方框中点权值最大. 题解: 离散化横纵坐标,容易将这个问题转化为在矩阵上求最大和子矩阵的问题. 普通的n*n的矩阵的 ...
- ultis, BIT(x), BITCOUNT(x)
/* http://resnet.uoregon.edu/~gurney_j/jmpc/bitwise.html */ #define BITCOUNT(x) (((BX_(x)+(BX_(x)> ...
- 20140321 sizeof 虚函数与虚函数表 静态数组空间 动态数组空间 位字段
1.静态的数组空间char a[10];sizeof 不能用于1:函数类型 2:动态的数组空间new3:位字段 函数类型:int fun();sizeof(fun())计算的是返回类型的大小,并不是函 ...
- pyhton2与python3的使用区别
刚刚开始学习python这门编程语言,考虑到python不同版本的一些用法不同,收集整理了一份python2与python3之间的区别,目前可能不全 编码(核心类) Python2默认编码ascii, ...
- 【牛客提高训练营5B】旅游
题目 吉老师的题时过一年还是不会做 从\(1\)号点出发经过每条边至少一次并且还要回到\(1\)号点,这跟欧拉回路的条件非常像,但是欧拉回路的实际上是"经过每一条边恰好一次并且回到出发点&q ...
- jdbc_mysql----interset
- git mac安装
1.git安装包安装 去官网下载最行的git版本 安装即可 https://git-scm.com/download/mac 但是一般的git仓库需要sshkey来做验证 下面奉上具体的命令: 需要生 ...
- SQL一些记录
1,2字段约束create unique index [索引名] on 软件信息表(S_SName,S_Edition)
- postgresql使用pg_dump和pg_restore 实现跨服务器的数据库迁移或备份
因为业务需求,需要将服务器上的postgre多个数据库的数据整个库得迁移到另一个postgre数据库上. 一般表较少时,会使用postgre 的copy to 和 copy from 命令就能完成表的 ...
- Perl 环境安装
Perl 环境安装 在我们开始学习 Perl 语言前,我们需要先安装 Perl 的执行环境. Perl 可以在以下平台下运行: Unix (Solaris, Linux, FreeBSD, AIX, ...