console
const demoCode = `
const directions = ["left", "right", "up", "down"]
async function runGame() {
for (let count = 0; count < 500; count++) {
const direction = directions[Math.floor(Math.random() * 4)]
console.log(direction)
await game.move(direction)
}
}
setInterval(() => {
game.data.player.position.x = 7
game.data.player.position.y = 12
}, 30000)
runGame()
`
interpreter.run(demoCode)
// const game = interpreter.exports.gameObject
folderState.addMonitor(game, "name", {
label: "游戏名称"
})
<canvas id="ok" width="375" height="625"></canvas>
<div class="description">
<ul>
<li>✅ 关卡数据通过 tweakpane 绑定显示</li>
<li>✅ 关卡运行控制按钮(开始、结束、重新开始、暂停、继续运行、运行速度)</li>
<li>✅ 关卡状态数据由一个 reactive 对象统一管理</li>
<li>✅ 关卡状态由数据驱动运行</li>
<li>✅ 关卡自动随机运行</li>
<li>✅ 带有 pause/restart 功能的 dealy 函数</li>
<li>⚪ 关卡状态可随时从给出的数据中恢复(存档)</li>
<li>⚪ </li>
</ul>
</div>
<script>
const LEVEL = Vue.reactive({
autoplay: true,
isPause: false,
speed: 500,
money: 0,
player: {
position: {
x: 7,
y: 12
}
},
npcList: [
{ position: { x: 1, y: 1 }, colorIndex: 0 },
{ position: { x: 2, y: 2 }, colorIndex: 1 },
{ position: { x: 3, y: 3 }, colorIndex: 2 },
{ position: { x: 4, y: 4 }, colorIndex: 3 },
{ position: { x: 5, y: 5 }, colorIndex: 4 },
{ position: { x: 6, y: 6 }, colorIndex: 5 },
]
})
class Game {
name = "探险家"
zr = zrender.init(document.getElementById("ok"), {
devicePixelRatio: 0.5,
width: 375 * 2,
height: 625 * 2
})
screenColumn = 15
screenRow = 25
tileSize = 50
canvasWidth = 15 * 25 * 2
canvasHeight = 25 * 25 * 2
gridColor = "#CCC"
playerColor = "#000"
npcColors = ["#F00", "#FF0", "#0F0", "#0FF", "#F0F", "#00F"]
gridGroup = new zrender.Group()
npcGroup = new zrender.Group()
player = null
data = LEVEL
delayCheckInterval = 200
delayThreshold = 0
delayCounter = 0
constructor() {
this.setSpeed()
this.zr.add(this.gridGroup)
this.zr.add(this.npcGroup)
this.player = this.createTile(this.data.player.position, this.playerColor)
this.zr.add(this.player)
this.drawGrid()
this.data.npcList.forEach(npc => {
this.npcGroup.add(this.createTile(npc.position, this.npcColors[npc.colorIndex]))
})
const that = this
setInterval(function() {
that.data.money++
}, 1000)
}
drawGrid() {
for (let h = 0; h <= this.screenColumn; h++) {
const vLine = new zrender.Line({
shape: {
x1: h * this.tileSize,
y1: 0,
x2: h * this.tileSize,
y2: this.canvasHeight
},
silent: true
})
this.gridGroup.add(vLine)
}
for (let v = 0; v <= this.screenRow; v++) {
const hLine = new zrender.Line({
shape: {
x1: 0,
y1: v * this.tileSize,
x2: this.canvasWidth,
y2: v * this.tileSize
},
silent: true
})
this.gridGroup.add(hLine)
}
}
createTile(position = { x: 0, y: 0 }, color = this.npcColors[0]) {
return new zrender.Rect({
shape: {
x: position.x * this.tileSize,
y: position.y * this.tileSize,
width: this.tileSize,
height: this.tileSize
},
style: {
fill: color
},
silent: true
})
}
pause() {
this.data.isPause = true
}
resume() {
this.data.isPause = false
}
setSpeed(speed) {
if (speed) this.data.speed = speed
this.delayThreshold = Math.floor(this.data.speed / this.delayCheckInterval)
}
checkBeforeExcute() {
const that = this
return new Promise((resolve, reject) => {
console.log(that.delayCounter * that.delayCheckInterval)
that.delayCounter++
setTimeout(function() {
if (that.delayCounter > that.delayThreshold && !that.data.isPause) {
that.delayCounter = 0
resolve()
} else {
reject()
}
}, this.delayCheckInterval)
})
.catch(err => {
return that.checkBeforeExcute()
})
}
async move(direction = "left") {
if (this.data.speed > 0 || this.data.isPause) {
await this.checkBeforeExcute()
}
switch(direction) {
case "left":
if (this.data.player.position.x - 1 >= 0) this.data.player.position.x--
break
case "right":
if (this.data.player.position.x + 1 < this.screenColumn) this.data.player.position.x++
break
case "up":
if (this.data.player.position.y - 1 >= 0) this.data.player.position.y--
break
case "down":
if (this.data.player.position.y + 1 < this.screenRow) this.data.player.position.y++
}
this.player.animate("shape", false)
.when(this.data.speed, {
x: this.data.player.position.x * this.tileSize,
y: this.data.player.position.y * this.tileSize
})
.start()
console.log(`move ${direction} ok!`)
}
}
const game = new Game()
const interpreter = new Sval({
ecmaVer: 9,
sandBox: true
})
interpreter.import("game", game)
</script>
<script>
const pane = new Tweakpane.Pane()
const folderControl = pane.addFolder({
title: "状态控制"
})
const folderParam = pane.addFolder({
title: "可调节参数"
})
const folderState = pane.addFolder({
title: "状态数据"
})
const playButton = folderControl.addButton({
title: "开始"
})
const pauseButton = folderControl.addButton({
title: "暂停"
})
const resumeButton = folderControl.addButton({
title: "继续",
disabled: true
})
const restartButton = folderControl.addButton({
title: "重新开始"
})
const stopButton = folderControl.addButton({
title: "结束"
})
pauseButton.on("click", function() {
pauseButton.disabled = true
resumeButton.disabled = false
LEVEL.isPause = true
console.log("-------------> 暂停!")
})
resumeButton.on("click", function() {
pauseButton.disabled = false
resumeButton.disabled = true
LEVEL.isPause = false
console.log("-------------> 继续!")
})
const autoplayInput = folderParam.addInput(LEVEL, "autoplay", {
label: "自动开始"
})
const speedInput = folderParam.addInput(LEVEL, "speed", {
label: "运行速度",
options: {
"正常 500": 500,
"放慢 1000": 1000,
"极慢 2500": 2500,
"加快 250": 250,
"极快 100": 100
}
})
autoplayInput.on("change", function(e) {
console.log(`autoplay -> ${e.value}`)
console.log(LEVEL.autoplay)
})
speedInput.on("change", function(e) {
game.setSpeed(e.value)
console.log(`-------------> 速度 ${e.value} !`)
})
folderState.addMonitor(LEVEL.player.position, "x", {
label: "玩家 x"
})
folderState.addMonitor(LEVEL.player.position, "y", {
label: "玩家 y"
})
folderState.addMonitor(LEVEL, "speed", {
label: "运行速度"
})
folderState.addMonitor(LEVEL, "money", {
label: "金钱"
})
</script>
<script>
Vue.watchEffect(() => {
pane.refresh()
})
</script>
canvas#ok {
background: white;
}
ul {
list-style-type: none;
padding-left: 0;
}