所以我正在尝试创建一个基本的CSS游戏。我能够让它在没有任何额外屏幕的情况下工作(游戏只是在它打开的HTML打开的那一刻开始),现在我正在尝试添加其他功能(画布中的第一个屏幕会有一个按钮)开始游戏并在游戏结束后,画布上会出现一个新的屏幕,其中包含“再试一次”按钮。
我认为我可以通过将游戏板(曾经在全局范围内/在任何函数之外)初始化为“GameStart()”函数来实现,但由于某种原因它不能正常工作。
代码正在调用我的GameStart()函数(通过在关键点放置警报来测试它),但是我无法让它调用我的draw()函数来启动实际的游戏和循环。
这是我的代码,虽然我冒昧地不包括与游戏如何工作相关的功能(draw()使用的功能)因为它们在这里只是作为垃圾邮件。
编辑: 为了澄清,我想问的是,是否可以通过这种方式为画布游戏合并不同的屏幕,或者是否存在与此完全不同的“正确”方式?
<script>
var canvas = document.getElementById("Canvas");
var ctx = canvas. getContext("2d");
function GameStart()
{
// clear previous canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// ball variables here
var dx = 0;
var dy = 0;
var ballRadius = 7;
var x = canvas.width/2;
var y = canvas.height/2;
// paddle variables here
var paddleHeight = 10;
var paddleWidth = 125;
var paddleX = (canvas.width-paddleWidth)/2;
var rightbutton = false;
var leftbutton = false;
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
document.addEventListener("mousemove", mouseMoveHandler, false);
// destroyable blockfields here
var brickRowCount = 6;
var brickColumnCount = 20;
var brickWidth = 45;
var brickHeight = 45;
var brickPadding = 0;
var brickOffsetTop = 30;
var brickOffsetLeft = 30;
var bricks=[];
for(c=0; c<brickColumnCount; c++)
{
bricks[c] = [];
for(r=0; r<brickRowCount; r++)
{
bricks[c][r] = { x: 0, y: 0, status: 1 };
}
}
// player here
var score = 0;
var lives = 3;
var destroyed = 0;
var dscore = 1;
var endscore = 0;
draw();
}
...这里有一堆与游戏相关的功能,但是它们不应该是问题的原因,因为当我没有尝试添加“GameStart()”函数时它们工作得很好......
function draw() {
alert("Drawing");
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBall();
drawPaddle();
collisionDetection();
drawBricks();
drawScore();
drawLives();
x += dx;
y += dy;
if (dy < 2 && dy >= 0)
{
dy += 0.05;
}
if (score > 0)
{
score=score-dscore;
}
if(rightbutton && paddleX < canvas.width-paddleWidth)
{
paddleX +=5;
}
else if(leftbutton && paddleX > 0)
{
paddleX -=5;
}
requestAnimationFrame(draw);
}
</script>
我对代码的唯一改变是我将之前没有任何函数内部的动作绑定到GameStart()函数中,并将draw()-call从脚本末尾移动到GameStart中()-function并添加了GameStart()函数调用到脚本的末尾
答案 0 :(得分:0)
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: black;
}
canvas {
position: absolute;
margin: auto;
left: 0;
right: 0;
border: solid 1px white;
border-radius: 10px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="application/javascript">
/*
This here is a simple 'module pattern', it's a
function that is immiediately invoked.
so the variable stateManager is having the function's
return object assigned to it.
JS has a concept called 'closures'
In a nutshell variables created inside this function
when used by methods in stateManager won't be forgotten
This effectively allows us to create what is called a namespace
in other languages, private scope.
e.g.
var module = (function() {
"use strict";
This is a hint for the JS engine to enforce strict mode
It just means to enforce better syntax rules
e.g. value = 10; instead of var value = 10; will create an error.
var value = 10;
return {
getValue: function() {
return value;
}
};
})();
module.getValue(); <- returns 10
*/
// Encapsulates some simple UI for an individual state
var UI = (function() {
function UI() {
this.labels = [];
this.buttons = [];
this.fontHeight = 0;
}
UI.prototype = {
createLabel: function(labelID,labelText,x,y,FG,BG) {
var label = {
ID: labelID || "" + this.labels.length,
text: labelText || "",
x: x || 0,
y: y || 0,
width: 0,
height: 0,
FG: FG || "black",
BG: BG || "black"
};
this.labels[labelID] = label;
this.labels.push(label);
},
createButton: function(buttonID,buttonText,x,y,width,height,FG,BG,pressedBG,callback) {
var button = {
ID: buttonID || "" + this.buttons.length,
text: buttonText || "",
x: x || 0,
y: y || 0,
width: width || 0,
height: height || 0,
FG: FG || "black",
BG: BG || "black",
pressedBG: pressedBG || "black",
callback: callback || function() {},
isPressed: false
};
this.buttons[buttonID] = button;
this.buttons.push(button);
},
setLabelText(labelID,newText) {
var label = this.labels[labelID];
if (label) {
label.text = newText;
label.width = this.ctx.measureText(newText).width + 4;
label.height = this.fontHeight;
}
},
setButtonText(buttonID,newText) {
var button = this.buttons[buttonID];
if (button) {
button.text = newText;
}
},
setFont: function(ctx,newFont,fontSize) {
this.ctx = ctx;
ctx.font = (fontSize|0) + "px " + (newFont || "Arial");
this.fontHeight = fontSize;
for (var i = 0; i < this.labels.length; ++i) {
var label = this.labels[i];
label.width = ctx.measureText(label.text).width + 4;
label.height = fontSize * 1.2;
}
},
onmousedown: function(x,y) {
for (var i = 0; i < this.buttons.length; ++i) {
var button = this.buttons[i];
var isOverlapping = x > button.x && x < button.x + button.width
&& y > button.y && y < button.y + button.height;
if (isOverlapping) {
button.isPressed = true;
return;
}
}
},
onmouseup: function(x,y) {
for (var i = 0; i < this.buttons.length; ++i) {
var button = this.buttons[i];
var isOverlapping = x > button.x && x < button.x + button.width
&& y > button.y && y < button.y + button.height;
if (isOverlapping && button.isPressed) {
button.isPressed = false;
button.callback();
return;
}
button.isPressed = false;
}
},
render: function(ctx) {
for (var i = 0; i < this.labels.length; ++i) {
var label = this.labels[i];
ctx.fillStyle = label.BG;
ctx.fillRect(
label.x - label.width * 0.5,
label.y - label.height * 0.5 - this.fontHeight * 0.25,
label.width,
label.height
);
ctx.fillStyle = label.FG;
ctx.fillText(label.text,label.x,label.y);
}
for (var i = 0; i < this.buttons.length; ++i) {
var button = this.buttons[i];
ctx.fillStyle = button.isPressed ? button.pressedBG : button.BG;
ctx.fillRect(
button.x,
button.y,
button.width,
button.height
);
ctx.fillStyle = button.FG;
ctx.fillText(
button.text,
button.x + button.width * 0.5,
button.y + button.height * 0.5 + this.fontHeight * 0.25
);
}
}
};
return UI;
}());
// Use to create and switch between different game screens
var state = (function() {
"use strict";
var bgColour = "#444444";
var canvasWidth = 0;
var canvasHeight = 0;
var canvas = null;
var ctx = null;
var bounds = null;
var hasStarted = false;
var currentState = null;
var stateMap = {}; // Stores all states
var stateStack = []; // Holds trace of states that have been pushed on each other
function onmousemoved(e) {
var x = e.clientX - bounds.left;
var y = e.clientY - bounds.top;
if (currentState.onmousemoved) {
currentState.onmousemoved(x,y);
}
if (e.button === 1 && currentState.ui) {
ui.onmousedown(x,y);
}
}
function onmousedown(e) {
var x = e.clientX - bounds.left;
var y = e.clientY - bounds.top;
var b = e.button;
if (currentState.onmousedown) {
currentState.onmousedown(x,y,b);
}
if (currentState.ui) {
currentState.ui.onmousedown(x,y);
}
}
function onmouseup(e) {
var x = e.clientX - bounds.left;
var y = e.clientY - bounds.top;
var b = e.button;
if (currentState.onmouseup) {
currentState.onmousemoved(x,y,b);
}
if (currentState.ui) {
currentState.ui.onmouseup(x,y);
}
}
function onkeydown(e) {
if (currentState.onkeydown) {
currentState.onkeydown(e);
}
}
function onkeyup(e) {
if (currentState.onkeyup) {
currentState.onkeyup(e);
}
}
function loop() {
if (currentState.ontick) {
currentState.ontick();
}
ctx.fillStyle = bgColour;
ctx.fillRect(0,0,canvasWidth,canvasHeight);
for (var i = 0; i < stateStack.length; ++i) {
var state = stateStack[i];
if (state.onrender) {
state.onrender(ctx);
}
if (state.ui) {
state.ui.render(ctx);
}
}
requestAnimationFrame(loop);
}
function start() {
for (var stateID in stateMap) {
var state = stateMap[stateID];
if (state.oncreate) {
state.oncreate();
if (state.ui) {
state.ui.setFont(ctx,"Courier New",14);
}
}
}
addEventListener("mousemoved",onmousemoved);
addEventListener("mousedown",onmousedown);
addEventListener("mouseup",onmouseup);
addEventListener("keydown",onkeydown);
addEventListener("keyup",onkeyup);
requestAnimationFrame(loop);
}
return {
setCanvas: function(_canvas,_width,_height) {
canvas = _canvas;
canvasWidth = _width;
canvasHeight = _height;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
ctx = canvas.getContext("2d");
ctx.textAlign = "center";
bounds = canvas.getBoundingClientRect();
},
setBackground: function(r,g,b) {
bgColour = "#" + (r|0).toString(16) + (g|0).toString(16) + (b|0).toString(16);
},
// Add a new state
define: function(stateID,newState) {
stateMap[stateID] = newState;
},
// Manually switch the current state (clears the stack)
switch: function(stateID) {
var state = stateMap[stateID];
if (state) {
for (var i = 0; i < stateStack.length; ++i) {
var stackedState = stateStack[i];
if (stackedState.onend) {
stackedState.onend();
}
}
stateStack.length = 1;
stateStack[0] = state;
currentState = state;
if (currentState.onstart) {
currentState.onstart();
}
if (!hasStarted) {
hasStarted = true;
start();
}
}
},
// add/remove from the state stack
push: function(stateID) {
var state = stateMap[stateID];
if (state && stateStack.indexOf(state) === -1) {
stateStack.push(state);
if (hasStarted && currentState.onpause) {
currentState.onpause();
}
currentState = stateStack[stateStack.length - 1];
if (currentState.onstart) {
currentState.onstart();
}
if (!hasStarted) {
hasStarted = true;
start();
}
}
},
pop: function() {
if (stateStack.length > 1) {
stateStack.pop();
if (currentState.onend) {
currentState.onend();
}
currentState = stateStack[stateStack.length - 1];
if (currentState.onresume) {
currentState.onresume();
}
}
}
};
})();
</script>
<script type="application/javascript">
/*
This is similar to the module pattern above but
it's strictly being used to provide a private scope
*/
void function() {
"use strict";
var canvasWidth = 180;
var canvasHeight = 160;
// Define game screens
state.define("mainMenu",{
oncreate: function() {
var ui = new UI();
ui.createLabel(
"mainMenuLabel", // id
"Main Menu", // text
90, // x
20, // y
"white", // text colour
"darkgreen" // background colour
);
ui.createButton(
"startGameButton", // id
"Start Game", // text
45, // x
55, // y
90, // width
30, // height
"white", // text colour
"#222222", // background colour
"#555555", // background colour (when clicked on)
function() { // function to run when clicked
state.switch("gameState");
}
);
ui.createButton(
"openOptionsButton",
"Options",
45,
95,
90,
30,
"white",
"#222222",
"#555555",
function() {
state.switch("optionsMenu");
}
);
this.ui = ui;
},
});
var editingKey = '';
var upControl = 'w';
var downControl = 's';
var leftControl = 'a';
var rightControl = 'd';
state.define("optionsMenu",{
oncreate: function() {
var ui = new UI();
ui.createLabel(
"optionsMenuLabel",
"Options Menu",
90,
20,
"white",
"darkgreen"
);
ui.createButton(
"upButton",
"Up: '" + upControl + "'",
45,
30,
90,
20,
"white",
"#222222",
"#555555",
function() {
editingKey = "up";
ui.setButtonText("upButton","Up: '?'");
ui.setButtonText("downButton","Down: '" + downControl + "'");
ui.setButtonText("leftButton","Left: '" + leftControl + "'");
ui.setButtonText("rightButton","Right: '" + rightControl + "'");
}
);
ui.createButton(
"downButton",
"Down: '" + downControl + "'",
45,
55,
90,
20,
"white",
"#222222",
"#555555",
function() {
editingKey = "down";
ui.setButtonText("upButton","Up: '" + upControl + "'");
ui.setButtonText("downButton","Down: '?'");
ui.setButtonText("leftButton","Left: '" + leftControl + "'");
ui.setButtonText("rightButton","Right: '" + rightControl + "'");
}
);
ui.createButton(
"leftButton",
"Left: '" + leftControl + "'",
45,
80,
90,
20,
"white",
"#222222",
"#555555",
function() {
editingKey = "left";
ui.setButtonText("upButton","Up: '" + upControl + "'");
ui.setButtonText("downButton","Down: '" + downControl + "'");
ui.setButtonText("leftButton","Left: '?'");
ui.setButtonText("rightButton","Right: '" + rightControl + "'");
}
);
ui.createButton(
"rightButton",
"Right: '" + rightControl + "'",
45,
105,
90,
20,
"white",
"#222222",
"#555555",
function() {
editingKey = "right";
ui.setButtonText("upButton","Up: '" + upControl + "'");
ui.setButtonText("downButton","Down: '" + downControl + "'");
ui.setButtonText("leftButton","Left: '" + leftControl + "'");
ui.setButtonText("rightButton","Right: '?'");
}
);
ui.createButton(
"backButton",
"Back",
68.5,
135,
45,
15,
"white",
"#222222",
"#555555",
function() {
ui.setButtonText("upButton","Up: '" + upControl + "'");
ui.setButtonText("downButton","Down: '" + downControl + "'");
ui.setButtonText("leftButton","Left: '" + leftControl + "'");
ui.setButtonText("rightButton","Right: '" + rightControl + "'");
state.switch("mainMenu");
}
);
this.ui = ui;
},
onkeydown: function(e) {
switch(editingKey) {
case "up": {
editingKey = "";
upControl = e.key;
this.ui.setButtonText("upButton","Up: '" + upControl + "'");
} break;
case "down": {
editingKey = "";
downControl = e.key;
this.ui.setButtonText("downButton","Down: '" + downControl + "'");
} break;
case "left": {
editingKey = "";
leftControl = e.key;
this.ui.setButtonText("leftButton","Left: '" + leftControl + "'");
} break;
case "right": {
editingKey = "";
rightControl = e.key;
this.ui.setButtonText("rightButton","Right: '" + rightControl + "'");
} break;
}
}
});
var player = {
x: 90,
y: 80,
speed: 1.0,
radius: 10,
up: false,
down: false,
left: false,
right: false,
tick: function() {
if (this.up) { this.y -= this.speed; }
if (this.down) { this.y += this.speed; }
if (this.left) { this.x -= this.speed; }
if (this.right) { this.x += this.speed; }
},
render: function(ctx) {
ctx.fillStyle = "darkred";
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(this.x,this.y,this.radius,0.0,2.0*Math.PI,false);
ctx.fill();
ctx.stroke();
}
};
state.define("gameState",{
oncreate: function() {
var ui = new UI();
ui.createButton(
"backButton",
"Quit",
canvasWidth - 55,
10,
45,
15,
"white",
"#222222",
"#555555",
function() {
state.switch("mainMenu");
}
);
this.ui = ui;
},
onkeydown: function(e) {
switch(e.key) {
case upControl: player.up = true; break;
case downControl: player.down = true; break;
case leftControl: player.left = true; break;
case rightControl: player.right = true; break;
}
},
onkeyup: function(e) {
switch(e.key) {
case upControl: player.up = false; break;
case downControl: player.down = false; break;
case leftControl: player.left = false; break;
case rightControl: player.right = false; break;
}
},
ontick: function() {
player.tick();
},
onrender: function(ctx) {
ctx.fillStyle = "darkblue";
ctx.fillRect(0,0,canvasWidth,canvasHeight);
player.render(ctx);
}
});
/*
This function runs when the page
finishes loading, it isn't essential
but I like to use it as a 'main method'/entry point
*/
window.onload = function() {
state.setCanvas(document.getElementById("canvas"),canvasWidth,canvasHeight);
state.switch("mainMenu");
}
}();
</script>
</body>
</html>
&#13;