Vamos relembrar o código que fizemos na parte anterior. Temos 3 arquivos:
index.html
<!DOCTYPE HTML>
<HTML>
<HEAD>
<TITLE></TITLE>
<LINK rel="stylesheet" href="style.css">
<meta charset="utf-8">
</HEAD>
<BODY>
<div id="container">
<div id="world">
</div>
<div id="pawn"></div>
</div>
</BODY>
</HTML>
<script src="script.js"></script>
style.css
#container{
position:absolute;
width:1200px;
height:800px;
border:2px solid #000000;
perspective:600px;
overflow:hidden;
}
#world{
position:absolute;
width:inherit;
height:inherit;
transform-style:preserve-3d;
}
.square{
position:absolute;
}
#pawn{
display:none;
position:absolute;
top:400px;
left:600px;
transform:translate(-50%,-50%);
width:100px;
height:100px;
}
script.js
//
var pi = 3.141592;
var deg = pi/180;
// player
function player(x,y,z,rx,ry) {
this.x = x;
this.y = y;
this.z = z;
this.rx = rx;
this.ry = ry;
}
//
var map = [
[0,0,1000,0,180,0,2000,200,"#F0C0FF"],
[0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
[1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
[-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
[0,100,0,90,0,0,2000,2000,"#666666"]
];
// ?
var PressBack = 0;
var PressForward = 0;
var PressLeft = 0;
var PressRight = 0;
var PressUp = 0;
var MouseX = 0;
var MouseY = 0;
// ?
var lock = false;
// ?
var onGround = true;
// container
var container = document.getElementById("container");
//
document.addEventListener("pointerlockchange", (event)=>{
lock = !lock;
});
//
container.onclick = function(){
if (!lock) container.requestPointerLock();
};
//
document.addEventListener("keydown", (event) =>{
if (event.key == "a"){
PressLeft = 1;
}
if (event.key == "w"){
PressForward = 1;
}
if (event.key == "d"){
PressRight = 1;
}
if (event.key == "s"){
PressBack = 1;
}
if (event.keyCode == 32 && onGround){
PressUp = 1;
}
});
//
document.addEventListener("keyup", (event) =>{
if (event.key == "a"){
PressLeft = 0;
}
if (event.key == "w"){
PressForward = 0;
}
if (event.key == "d"){
PressRight = 0;
}
if (event.key == "s"){
PressBack = 0;
}
if (event.keyCode == 32){
PressUp = 0;
}
});
//
document.addEventListener("mousemove", (event)=>{
MouseX = event.movementX;
MouseY = event.movementY;
});
//
var pawn = new player(-900,0,-900,0,0);
// world
var world = document.getElementById("world");
function update(){
//
dx = (PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg);
dz = - (PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg);
dy = - PressUp;
drx = MouseY;
dry = - MouseX;
// :
MouseX = MouseY = 0;
//
collision();
//
pawn.x = pawn.x + dx;
pawn.y = pawn.y + dy;
pawn.z = pawn.z + dz;
console.log(pawn.x + ":" + pawn.y + ":" + pawn.z);
// ,
if (lock){
pawn.rx = pawn.rx + drx;
pawn.ry = pawn.ry + dry;
};
// ( )
world.style.transform =
"translateZ(" + (600 - 0) + "px)" +
"rotateX(" + (-pawn.rx) + "deg)" +
"rotateY(" + (-pawn.ry) + "deg)" +
"translate3d(" + (-pawn.x) + "px," + (-pawn.y) + "px," + (-pawn.z) + "px)";
};
function CreateNewWorld(){
for (let i = 0; i < map.length; i++){
//
let newElement = document.createElement("div");
newElement.className = "square";
newElement.id = "square" + i;
newElement.style.width = map[i][6] + "px";
newElement.style.height = map[i][7] + "px";
newElement.style.background = map[i][8];
newElement.style.transform = "translate3d(" +
(600 - map[i][6]/2 + map[i][0]) + "px," +
(400 - map[i][7]/2 + map[i][1]) + "px," +
(map[i][2]) + "px)" +
"rotateX(" + map[i][3] + "deg)" +
"rotateY(" + map[i][4] + "deg)" +
"rotateZ(" + map[i][5] + "deg)";
// world
world.append(newElement);
}
}
function collision(){
for(let i = 0; i < map.length; i++){
//
let x0 = (pawn.x - map[i][0]);
let y0 = (pawn.y - map[i][1]);
let z0 = (pawn.z - map[i][2]);
if ((x0**2 + y0**2 + z0**2 + dx**2 + dy**2 + dz**2) < (map[i][6]**2 + map[i][7]**2)){
let x1 = x0 + dx;
let y1 = y0 + dy;
let z1 = z0 + dz;
let point0 = coorTransform(x0,y0,z0,map[i][3],map[i][4],map[i][5]);
let point1 = coorTransform(x1,y1,z1,map[i][3],map[i][4],map[i][5]);
let point2 = new Array();
//
if (Math.abs(point1[0])<(map[i][6]+98)/2 && Math.abs(point1[1])<(map[i][7]+98)/2 && Math.abs(point1[2]) < 50){
point1[2] = Math.sign(point0[2])*50;
point2 = coorReTransform(point1[0],point1[1],point1[2],map[i][3],map[i][4],map[i][5]);
dx = point2[0] - x0;
dy = point2[1] - y0;
dz = point2[2] - z0;
}
}
};
}
function coorTransform(x0,y0,z0,rxc,ryc,rzc){
let x1 = x0;
let y1 = y0*Math.cos(rxc*deg) + z0*Math.sin(rxc*deg);
let z1 = -y0*Math.sin(rxc*deg) + z0*Math.cos(rxc*deg);
let x2 = x1*Math.cos(ryc*deg) - z1*Math.sin(ryc*deg);
let y2 = y1;
let z2 = x1*Math.sin(ryc*deg) + z1*Math.cos(ryc*deg);
let x3 = x2*Math.cos(rzc*deg) + y2*Math.sin(rzc*deg);
let y3 = -x2*Math.sin(rzc*deg) + y2*Math.cos(rzc*deg);
let z3 = z2;
return [x3,y3,z3];
}
function coorReTransform(x3,y3,z3,rxc,ryc,rzc){
let x2 = x3*Math.cos(rzc*deg) - y3*Math.sin(rzc*deg);
let y2 = x3*Math.sin(rzc*deg) + y3*Math.cos(rzc*deg);
let z2 = z3
let x1 = x2*Math.cos(ryc*deg) + z2*Math.sin(ryc*deg);
let y1 = y2;
let z1 = -x2*Math.sin(ryc*deg) + z2*Math.cos(ryc*deg);
let x0 = x1;
let y0 = y1*Math.cos(rxc*deg) - z1*Math.sin(rxc*deg);
let z0 = y1*Math.sin(rxc*deg) + z1*Math.cos(rxc*deg);
return [x0,y0,z0];
}
CreateNewWorld();
TimerGame = setInterval(update,10);
1. Implementação da gravidade e física do salto
Temos várias variáveis que são criadas em diferentes partes do arquivo javascript. Será melhor se os movermos para um lugar:
//
var lock = false;
var onGround = true;
var container = document.getElementById("container");
var world = document.getElementById("world");
Vamos adicionar aceleração de queda livre a eles:
var g = 0.1;
Adicione 3 variáveis ao construtor do player - vx, vy e vz:
function player(x,y,z,rx,ry) {
this.x = x;
this.y = y;
this.z = z;
this.rx = rx;
this.ry = ry;
this.vx = 3;
this.vy = 5;
this.vz = 3;
}
Estas são velocidades variáveis. Ao alterá-los, podemos alterar a velocidade de corrida e a velocidade inicial de salto do jogador. Por enquanto, vamos aplicar as novas variáveis em update ():
dx = ((PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg))*pawn.vx;
dz = ( -(PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg))*pawn.vz;
dy = PressUp*pawn.vy;
drx = MouseY;
dry = - MouseX;
O jogador agora se move mais rápido. Mas ele não cai nem pula. É necessário permitir um salto quando estiver em algo. E permanecerá firme quando colidir com uma superfície horizontal (ou quase). Como definir horizontalidade? Precisamos encontrar a normal do plano do retângulo. Isso é feito de forma simples. Com relação às coordenadas do retângulo, a normal é direcionada ao longo do eixo z. Então o normal transformou as coordenadas em coordenadas mundiais. Encontre o normal (adicione a variável local normal):
let point0 = coorTransform(x0,y0,z0,map[i][3],map[i][4],map[i][5]);
let point1 = coorTransform(x1,y1,z1,map[i][3],map[i][4],map[i][5]);
let point2 = new Array();
let normal = coorReTransform(0,0,1,map[i][3],map[i][4],map[i][5]);
Para que a superfície seja horizontal, o produto escalar da normal para o eixo y nas coordenadas mundiais deve ser 1 ou -1 e o plano quase horizontal deve ser próximo a 1 ou -1. Vamos definir a condição para um plano quase horizontal:
if (Math.abs(normal[1]) > 0.8){
onGround = true;
}
Não se esqueça de que, na ausência de colisões, o jogador definitivamente não estará no solo, portanto, por padrão, no início da função collision (), defina onGround = false:
function collision(){
onGround = false;
for(let i = 0; i < map.length; i++){
No entanto, se o jogador colidir com a superfície por baixo, ele também aparecerá como se estivesse no chão. Para evitar isso, vamos verificar se o jogador está no topo do avião (o ponto 3 [1] deve ser menor que o ponto 2 [1]):
let point3 = coorReTransform(point1[0],point1[1],0,map[i][3],map[i][4],map[i][5]);
dx = point2[0] - x0;
dy = point2[1] - y0;
dz = point2[2] - z0;
if (Math.abs(normal[1]) > 0.8){
if (point3[1] > point2[1]) onGround = true;
}
else dy = y1 - y0;
O que estamos fazendo? dê uma olhada na imagem: a
cruz vermelha deve estar abaixo da laranja no sistema de coordenadas mundial (ou a coordenada y deve ser maior). Isso é o que verificamos no ponto 3 [1]> ponto 2 [1]. E o ponto 3 são apenas as coordenadas do ponto vermelho. Vamos mover a inicialização do point2 dentro da condição de colisão:
let point0 = coorTransform(x0,y0,z0,map[i][3],map[i][4],map[i][5]);
let point1 = coorTransform(x1,y1,z1,map[i][3],map[i][4],map[i][5]);
let normal = coorReTransform(0,0,1,map[i][3],map[i][4],map[i][5]);
//
if (Math.abs(point1[0])<(map[i][6]+98)/2 && Math.abs(point1[1])<(map[i][7]+98)/2 && Math.abs(point1[2]) < 50){
point1[2] = Math.sign(point0[2])*50;
let point2 = coorReTransform(point1[0],point1[1],point1[2],map[i][3],map[i][4],map[i][5]);
let point3 = coorReTransform(point1[0],point1[1],0,map[i][3],map[i][4],map[i][5]);
dx = point2[0] - x0;
dy = point2[1] - y0;
dz = point2[2] - z0;
if (Math.abs(normal[1]) > 0.8){
if (point3[1] > point2[1]) onGround = true;
}
}
Vamos prosseguir para update (). Faremos alterações aqui também. Primeiro, vamos adicionar gravidade e remover o deslocamento y ao pressionar a barra de espaço:
//
dx = ((PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg))*pawn.vx;
dz = ( -(PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg))*pawn.vz;
dy = dy + g;
drx = MouseY;
dry = - MouseX;
Em segundo lugar, se o jogador estiver no solo, proibimos a gravidade, proibimos o deslocamento em y (caso contrário, após caminhar em uma superfície inclinada, o jogador decolará) e adicionamos a capacidade de saltar (condição se (no terreno)):
//
dx = ((PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg))*pawn.vx;
dz = ( -(PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg))*pawn.vz;
dy = dy + g;
if (onGround){
dy = 0;
if (PressUp){
dy = - PressUp*pawn.vy;
onGround = false;
}
};
drx = MouseY;
dry = - MouseX;
Naturalmente, imediatamente após o salto, proibimos o salto repetido definindo o parâmetro onGround como falso. Na condição de barra de espaço, a validade deste parâmetro não é mais necessária:
if (event.keyCode == 32){
PressUp = 1;
}
Para testar as mudanças, vamos mudar o mundo:
var map = [
[0,0,1000,0,180,0,2000,200,"#F0C0FF"],
[0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
[1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
[-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
[0,0,-300,70,0,0,200,500,"#F000FF"],
[0,-86,-786,90,0,0,200,500,"#F000FF"],
[-500,0,-300,20,0,0,200,500,"#00FF00"],
[0,100,0,90,0,0,2000,2000,"#666666"]
];
Se iniciarmos o jogo, podemos ver que o jogador pode escalar uma parede verde quase vertical. Desative isto adicionando else dy = y1 - y0:
if (Math.abs(point1[0])<(map[i][6]+98)/2 && Math.abs(point1[1])<(map[i][7]+98)/2 && Math.abs(point1[2]) < 50){
point1[2] = Math.sign(point0[2])*50;
let point2 = coorReTransform(point1[0],point1[1],point1[2],map[i][3],map[i][4],map[i][5]);
let point3 = coorReTransform(point1[0],point1[1],0,map[i][3],map[i][4],map[i][5]);
dx = point2[0] - x0;
dy = point2[1] - y0;
dz = point2[2] - z0;
if (Math.abs(normal[1]) > 0.8){
if (point3[1] > point2[1]) onGround = true;
}
else dy = y1 - y0;
}
Portanto, as colisões com paredes altamente verticais não alteram o deslocamento y. Portanto, o overclock em tais paredes está agora completamente excluído. Vamos tentar escalar a parede verde. Não poderemos fazer isso agora. Então, descobrimos a gravidade e os saltos, e agora podemos escalar de forma bastante realista em superfícies ligeiramente inclinadas. Vamos verificar o código:
index.html
<!DOCTYPE HTML>
<HTML>
<HEAD>
<TITLE></TITLE>
<LINK rel="stylesheet" href="style.css">
<meta charset="utf-8">
</HEAD>
<BODY>
<div id="container">
<div id="world">
</div>
<div id="pawn"></div>
</div>
</BODY>
</HTML>
<script src="script.js"></script>
style.css
#container{
position:absolute;
width:1200px;
height:800px;
border:2px solid #000000;
perspective:600px;
overflow:hidden;
}
#world{
position:absolute;
width:inherit;
height:inherit;
transform-style:preserve-3d;
}
.square{
position:absolute;
}
#pawn{
display:none;
position:absolute;
top:400px;
left:600px;
transform:translate(-50%,-50%);
width:100px;
height:100px;
}
script.js
//
var pi = 3.141592;
var deg = pi/180;
// player
function player(x,y,z,rx,ry) {
this.x = x;
this.y = y;
this.z = z;
this.rx = rx;
this.ry = ry;
this.vx = 3;
this.vy = 5;
this.vz = 3;
}
//
var map = [
[0,0,1000,0,180,0,2000,200,"#F0C0FF"],
[0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
[1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
[-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
[0,0,-300,70,0,0,200,500,"#F000FF"],
[0,-86,-786,90,0,0,200,500,"#F000FF"],
[-500,0,-300,20,0,0,200,500,"#00FF00"],
[0,-800,0,90,0,0,500,500,"#00FF00"],
[0,-400,700,60,0,0,500,900,"#FFFF00"],
[0,100,0,90,0,0,2000,2000,"#666666"]
];
// ?
var PressBack = 0;
var PressForward = 0;
var PressLeft = 0;
var PressRight = 0;
var PressUp = 0;
var MouseX = 0;
var MouseY = 0;
//
var lock = false;
var onGround = false;
var container = document.getElementById("container");
var world = document.getElementById("world");
var g = 0.1;
var dx = dy = dz = 0;
//
document.addEventListener("pointerlockchange", (event)=>{
lock = !lock;
});
//
container.onclick = function(){
if (!lock) container.requestPointerLock();
};
//
document.addEventListener("keydown", (event) =>{
if (event.key == "a"){
PressLeft = 1;
}
if (event.key == "w"){
PressForward = 1;
}
if (event.key == "d"){
PressRight = 1;
}
if (event.key == "s"){
PressBack = 1;
}
if (event.keyCode == 32){
PressUp = 1;
}
});
//
document.addEventListener("keyup", (event) =>{
if (event.key == "a"){
PressLeft = 0;
}
if (event.key == "w"){
PressForward = 0;
}
if (event.key == "d"){
PressRight = 0;
}
if (event.key == "s"){
PressBack = 0;
}
if (event.keyCode == 32){
PressUp = 0;
}
});
//
document.addEventListener("mousemove", (event)=>{
MouseX = event.movementX;
MouseY = event.movementY;
});
//
var pawn = new player(0,-900,0,0,0);
function update(){
//
dx = ((PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg))*pawn.vx;
dz = ( -(PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg))*pawn.vz;
dy = dy + g;
if (onGround){
dy = 0;
if (PressUp){
dy = - PressUp*pawn.vy;
onGround = false;
}
};
drx = MouseY;
dry = - MouseX;
// :
MouseX = MouseY = 0;
//
collision();
//
pawn.x = pawn.x + dx;
pawn.y = pawn.y + dy;
pawn.z = pawn.z + dz;
// ,
if (lock){
pawn.rx = pawn.rx + drx;
pawn.ry = pawn.ry + dry;
};
// ( )
world.style.transform =
"translateZ(" + (600 - 0) + "px)" +
"rotateX(" + (-pawn.rx) + "deg)" +
"rotateY(" + (-pawn.ry) + "deg)" +
"translate3d(" + (-pawn.x) + "px," + (-pawn.y) + "px," + (-pawn.z) + "px)";
};
function CreateNewWorld(){
for (let i = 0; i < map.length; i++){
//
let newElement = document.createElement("div");
newElement.className = "square";
newElement.id = "square" + i;
newElement.style.width = map[i][6] + "px";
newElement.style.height = map[i][7] + "px";
newElement.style.background = map[i][8];
newElement.style.transform = "translate3d(" +
(600 - map[i][6]/2 + map[i][0]) + "px," +
(400 - map[i][7]/2 + map[i][1]) + "px," +
(map[i][2]) + "px)" +
"rotateX(" + map[i][3] + "deg)" +
"rotateY(" + map[i][4] + "deg)" +
"rotateZ(" + map[i][5] + "deg)";
// world
world.append(newElement);
}
}
function collision(){
onGround = false;
for(let i = 0; i < map.length; i++){
//
let x0 = (pawn.x - map[i][0]);
let y0 = (pawn.y - map[i][1]);
let z0 = (pawn.z - map[i][2]);
if ((x0**2 + y0**2 + z0**2 + dx**2 + dy**2 + dz**2) < (map[i][6]**2 + map[i][7]**2)){
let x1 = x0 + dx;
let y1 = y0 + dy;
let z1 = z0 + dz;
let point0 = coorTransform(x0,y0,z0,map[i][3],map[i][4],map[i][5]);
let point1 = coorTransform(x1,y1,z1,map[i][3],map[i][4],map[i][5]);
let normal = coorReTransform(0,0,1,map[i][3],map[i][4],map[i][5]);
//
if (Math.abs(point1[0])<(map[i][6]+98)/2 && Math.abs(point1[1])<(map[i][7]+98)/2 && Math.abs(point1[2]) < 50){
point1[2] = Math.sign(point0[2])*50;
let point2 = coorReTransform(point1[0],point1[1],point1[2],map[i][3],map[i][4],map[i][5]);
let point3 = coorReTransform(point1[0],point1[1],0,map[i][3],map[i][4],map[i][5]);
dx = point2[0] - x0;
dy = point2[1] - y0;
dz = point2[2] - z0;
if (Math.abs(normal[1]) > 0.8){
if (point3[1] > point2[1]) onGround = true;
}
else dy = y1 - y0;
}
}
};
}
function coorTransform(x0,y0,z0,rxc,ryc,rzc){
let x1 = x0;
let y1 = y0*Math.cos(rxc*deg) + z0*Math.sin(rxc*deg);
let z1 = -y0*Math.sin(rxc*deg) + z0*Math.cos(rxc*deg);
let x2 = x1*Math.cos(ryc*deg) - z1*Math.sin(ryc*deg);
let y2 = y1;
let z2 = x1*Math.sin(ryc*deg) + z1*Math.cos(ryc*deg);
let x3 = x2*Math.cos(rzc*deg) + y2*Math.sin(rzc*deg);
let y3 = -x2*Math.sin(rzc*deg) + y2*Math.cos(rzc*deg);
let z3 = z2;
return [x3,y3,z3];
}
function coorReTransform(x3,y3,z3,rxc,ryc,rzc){
let x2 = x3*Math.cos(rzc*deg) - y3*Math.sin(rzc*deg);
let y2 = x3*Math.sin(rzc*deg) + y3*Math.cos(rzc*deg);
let z2 = z3
let x1 = x2*Math.cos(ryc*deg) + z2*Math.sin(ryc*deg);
let y1 = y2;
let z1 = -x2*Math.sin(ryc*deg) + z2*Math.cos(ryc*deg);
let x0 = x1;
let y0 = y1*Math.cos(rxc*deg) - z1*Math.sin(rxc*deg);
let z0 = y1*Math.sin(rxc*deg) + z1*Math.cos(rxc*deg);
return [x0,y0,z0];
}
CreateNewWorld();
TimerGame = setInterval(update,10);
2. Crie um menu
Vamos criar o menu na forma de painéis html e blocos html. O design de todo o menu será aproximadamente o mesmo: o plano de fundo e o estilo dos botões podem ser definidos como comuns a todos. Então, vamos definir três painéis de menu: o menu principal, instruções e saída de resultados após a conclusão do jogo. As transições entre o menu, a transição para o mundo e vice-versa serão realizadas por scripts javascript. Para não sobrecarregar o arquivo script.js, crie um novo arquivo menu.js para as transições de menu e inclua-o em index.html:
<script src="menu.js"></script>
No contêiner, criaremos 3 itens que serão barras de menu:
<div id="container">
<div id = "world"></div>
<div id = "pawn"></div>
<div id = "menu1"></div>
<div id = "menu2"></div>
<div id = "menu3"></div>
</div>
Vamos estilizá-los adicionando propriedades para a classe “menu” ao style.css:
.menu{
display:none;
position:absolute;
width:inherit;
height:inherit;
background-color:#C0FFFF;
}
Adicione botões com legendas apropriadas ao menu (no arquivo index.html):
<div class = "menu" id = "menu1">
<div id="button1" class="button">
<p> </p>
</div>
<div id="button2" class="button">
<p></p>
</div>
</div>
<div class = "menu" id = "menu2">
<p style="font-size:30px; top:200px">
<strong>:</strong> <br>
w - <br>
s - <br>
d - <br>
a - <br>
- <br>
!!! !!!<br>
<strong>:</strong> <br>
</p>
<div id="button3" class="button">
<p></p>
</div>
</div>
<div class = "menu" id = "menu3">
<p id = "result" style="top:100px"></p>
<div id="button4" class="button">
<p> </p>
</div>
</div>
Para os botões, também definiremos estilos em style.css:
.button{
margin:0px;
position:absolute;
width:900px;
height:250px;
background-color:#FFF;
cursor:pointer;
}
.button:hover{
background-color:#DDD;
}
#button1{
top:100px;
left:150px;
}
#button2{
top:450px;
left:150px;
}
#button3{
top:450px;
left:150px;
}
#button4{
top:450px;
left:150px;
}
Mas não vemos o menu, pois eles têm o estilo display: none. Quando o jogo começa, um dos itens do menu deve estar visível, então no html do primeiro menu adicionaremos a entrada style = “display: block;”, e ficar assim seria assim:
<div class = "menu" id = "menu1" style = "display:block;">
O menu agora se parece com este:
Ótimo. Mas se clicarmos no botão, o cursor será capturado. Portanto, precisamos permitir a captura do mouse apenas no caso de um jogo. Para fazer isso, insira a variável canlock em script.js e adicione-a às variáveis de criação do item:
//
var lock = false;
var onGround = false;
var container = document.getElementById("container");
var world = document.getElementById("world");
var g = 0.1;
var dx = dy = dz = 0;
var canlock = false;
:
//
container.onclick = function(){
if (!lock && canlock) container.requestPointerLock();
};
Agora podemos clicar no menu. Vamos configurar as transições usando scripts no arquivo menu.js:
//
var menu1 = document.getElementById("menu1");
var menu2 = document.getElementById("menu2");
var menu3 = document.getElementById("menu3");
var button1 = document.getElementById("button1");
var button2 = document.getElementById("button2");
var button3 = document.getElementById("button3");
var button4 = document.getElementById("button4");
//
button2.onclick = function(){
menu1.style.display = "none";
menu2.style.display = "block";
}
button3.onclick = function(){
menu1.style.display = "block";
menu2.style.display = "none";
}
button4.onclick = function(){
menu1.style.display = "block";
menu3.style.display = "none";
}
Agora todos os botões do menu, exceto “iniciar o jogo”, funcionam. Agora vamos configurar o botão button1. Se você se lembrar, no arquivo script.js, as funções CreateNewWorld () e setInterval () são acionadas quando a página da web é carregada. Vamos removê-los daí. Só iremos chamá-los quando o button1 for pressionado. Vamos fazer isso:
button1.onclick = function(){
menu1.style.display = "none";
CreateNewWorld();
TimerGame = setInterval(update,10);
}
Criamos um menu. Sim, ainda é feio, mas melhora facilmente.
3. Vamos criar objetos e transição de níveis.
Primeiro, vamos definir as regras do jogo. Temos três tipos de itens: moedas (quadrados amarelos), chaves (quadrados vermelhos) e acabamento (quadrados azuis). As moedas trazem pontos. O jogador precisa encontrar a chave e só então chegar à linha de chegada. Se ele chegar à linha de chegada sem uma chave, receberá uma mensagem sobre a necessidade de primeiro encontrar a chave. Criaremos objetos da mesma forma que um mapa. Vamos escrevê-los usando arrays. Mas não faremos uma função separada para eles. Vamos apenas escrever uma nova função que organiza os elementos do mapa e do retângulo e transporta os comandos de CreateNewWorld (). Vamos chamá-lo de CreateSquares (). Portanto, vamos adicionar a seguinte entrada ao final do arquivo script.js:
function CreateSquares(squares,string){
for (let i = 0; i < squares.length; i++){
//
let newElement = document.createElement("div");
newElement.className = string + " square";
newElement.id = string + i;
newElement.style.width = squares[i][6] + "px";
newElement.style.height = squares[i][7] + "px";
newElement.style.background = squares[i][8];
newElement.style.transform = "translate3d(" +
(600 - squares[i][6]/2 + squares[i][0]) + "px," +
(400 - squares[i][7]/2 + squares[i][1]) + "px," +
(squares[i][2]) + "px)" +
"rotateX(" + squares[i][3] + "deg)" +
"rotateY(" + squares[i][4] + "deg)" +
"rotateZ(" + squares[i][5] + "deg)";
// world
world.append(newElement);
}
}
E vamos mudar o conteúdo de createNewWorld ():
function CreateNewWorld(){
CreateSquares(map,”map”);
}
A string é necessária para definir o id do nome. O jogo ainda não mudou nada. Agora vamos adicionar 3 arrays: moedas (coisas), chaves (chaves) e acabamento (acabamento). Vamos inseri-los logo após a matriz do mapa:
var things = [[900,50,-900,0,0,0,50,50,"#FFFF00"],
[-400,50,900,0,0,0,50,50,"#FFFF00"],
[-400,50,-300,0,0,0,50,50,"#FFFF00"]];
var keys = [[-100,50,600,0,0,0,50,50,"#FF0000"]];
var start = [[-900,0,-900,0,0]];
var finish = [[-900,50,900,0,0,0,50,50,"#00FFFF"]];
E em menu.js, vamos usar a função CreateSquares () dentro do manipulador para clicar no botão “button1”:
button1.onclick = function(){
menu1.style.display = "none";
CreateNewWorld();
CreateSquares(things,”thing”);
CreateSquares(keys,”key”);
CreateSquares(finish,”finish”);
TimerGame = setInterval(update,10);
canlock = true;
}
Agora vamos configurar o desaparecimento de objetos. No menu.js, vamos criar uma função para verificar as distâncias do player aos objetos:
function interact(objects,string){
for (i = 0; i < objects.length; i++){
let r = (objects[i][0] - pawn.x)**2 + (objects[i][1] - pawn.y)**2 + (objects[i][2] - pawn.z)**2;
if(r < (objects[i][7]**2)/4){
document.getElementById(string + i).style.display = "none";
document.getElementById(string + i).style.transform =
"translate3d(1000000px,1000000px,1000000px)";
};
};
}
Além disso, no mesmo arquivo, crie a função repeatFunction () e adicione os comandos a ela:
function repeatFunction(){
update();
interact(things,"thing");
interact(keys,"key");
}
E vamos executar sua chamada cíclica em setInterval dentro de button1:
TimerGame = setInterval(repeatFunction,10);
Agora os objetos desaparecem quando nos aproximamos deles. No entanto, eles não fazem absolutamente nada. E queremos que os pontos sejam adicionados a nós quando os quadrados amarelos são retirados, e quando os quadrados vermelhos são retirados, temos a oportunidade de pegar o azul e terminar o jogo. Vamos modificar a função interact ():
function interact(objects,string,num){
for (i = 0; i < objects.length; i++){
let r = (objects[i][0] - pawn.x)**2 + (objects[i][1] - pawn.y)**2 + (objects[i][2] - pawn.z)**2;
if(r < (objects[i][7]**2)){
document.getElementById(string + i).style.display = "none";
objects[i][0] = 1000000;
objects[i][1] = 1000000;
objects[i][2] = 1000000;
document.getElementById(string + i).style.transform =
"translate3d(1000000px,1000000px,1000000px)";
num[0]++;
};
};
}
Vamos alterar os parâmetros de entrada para chamadas para esta função:
function repeatFunction(){
update();
interact(things,"thing",m);
interact(keys,"key",k);
}
E no início do arquivo, adicione quatro novas variáveis:
var m = [0];
var k = [0];
var f = [0];
var score = 0;
Por que criamos arrays de um elemento e não apenas variáveis, você pergunta? A questão é que queríamos passar essas variáveis para interact () por referência, não por valor. Em javascript, as variáveis regulares são passadas apenas por valor e os arrays são passados por referência. Se passarmos apenas uma variável para interact (), então num será uma cópia da variável. Alterar num não mudará k ou m. E se passarmos um array, então num será uma referência ao array k ou m, e quando mudarmos num [0], então k [0] e m [0] mudarão. Claro que era possível criar 2 funções quase idênticas, mas é melhor conviver com uma, um pouco mais universal.
Para finalizar, você ainda precisa criar uma função separada:
function finishInteract(){
let r = (finish[0][0] - pawn.x)**2 + (finish[0][1] - pawn.y)**2 + (finish[0][2] - pawn.z)**2;
if(r < (finish[0][7]**2)){
if (k[0] == 0){
console.log(" ");
}
else{
clearWorld();
clearInterval(TimerGame);
document.exitPointerLock();
score = score + m[0];
k[0] = 0;
m[0] = 0;
menu1.style.display = "block";
};
};
};
E configure clearWorld () em script.js:
function clearWorld(){
world.innerHTML = "";
}
Como você pode ver, limpar o mundo é muito simples. Adicione finishInteract () a repeatFunction ():
function repeatFunction(){
update();
interact(things,"thing",m);
interact(keys,"key",k);
finishInteract();
}
O que está acontecendo em finishInteract ()? Se não pegamos a chave (k [0] == 0), então nada acontece ainda. Em caso afirmativo, o jogo termina e acontece o seguinte: o mundo é limpo, a função repeatFunction () para, o cursor para de ser capturado, o contador de teclas é zerado e vamos para o menu principal. Vamos verificar executando o jogo. Tudo está funcionando. No entanto, depois de clicar novamente no jogo, nos encontramos imediatamente na linha de chegada e alguns itens desaparecem. Isso ocorre porque não inserimos o local do spawn inicial do jogador, e os arrays mudam durante o jogo. Vamos adicionar um ponto de spawn para o jogador ao button1, ou seja, igualar suas coordenadas aos elementos da matriz start [0]:
button1.onclick = function(){
menu1.style.display = "none";
CreateNewWorld();
pawn.x = start[0][0];
pawn.y = start[0][1];
pawn.z = start[0][2];
pawn.rx = start[0][3];
pawn.rx = start[0][4];
CreateSquares(things,"thing");
CreateSquares(keys,"key");
CreateSquares(finish,"finish");
TimerGame = setInterval(repeatFunction,10);
canlock = true;
}
O jogador agora desova na origem. Mas a questão é: e se houver vários níveis no jogo? Vamos adicionar uma variável de nível ao menu.js:
//
var menu1 = document.getElementById("menu1");
var menu2 = document.getElementById("menu2");
var menu3 = document.getElementById("menu3");
var button1 = document.getElementById("button1");
var button2 = document.getElementById("button2");
var button3 = document.getElementById("button3");
var button4 = document.getElementById("button4");
var m = [0];
var k = [0];
var f = [0];
var score = 0;
var level = 0;
Vamos refazer o mapa de variáveis, coisas, chaves, início, fim dentro de script.js em matrizes, mudando ligeiramente seus nomes:
// 1
mapArray[0] = [
[0,0,1000,0,180,0,2000,200,"#F0C0FF"],
[0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
[1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
[-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
[0,0,-300,70,0,0,200,500,"#F000FF"],
[0,-86,-786,90,0,0,200,500,"#F000FF"],
[-500,0,-300,20,0,0,200,500,"#00FF00"],
[0,100,0,90,0,0,2000,2000,"#666666"]
];
thingsArray [0] = [[900,50,-900,0,0,0,50,50,"#FFFF00"],
[-400,50,900,0,0,0,50,50,"#FFFF00"],
[-400,50,-300,0,0,0,50,50,"#FFFF00"]];
keysArray [0] = [[-100,50,600,0,0,0,50,50,"#FF0000"]];
startArray[0] = [[-900,0,-900,0,0]];
finishArray [0] = [[-900,50,900,0,0,0,50,50,"#00FFFF"]];
Vamos adicionar o segundo nível:
// 2
mapArray [1] = [
[0,0,1000,0,180,0,2000,200,"#F0C0FF"],
[0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
[1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
[-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
[0,0,-300,70,0,0,200,500,"#F000FF"],
[0,-86,-786,90,0,0,200,500,"#F000FF"],
[-500,0,-300,20,0,0,200,500,"#00FF00"],
[0,100,0,90,0,0,2000,2000,"#666666"]
];
thingsArray [1] = [[900,50,-900,0,0,0,50,50,"#FFFF00"],
[-400,50,900,0,0,0,50,50,"#FFFF00"],
[-400,50,-300,0,0,0,50,50,"#FFFF00"]];
keysArray [1] = [[-100,50,600,0,0,0,50,50,"#FF0000"]];
startArray[1] = [[0,0,0,0,0]];
finishArray [1] = [[-900,50,900,0,0,0,50,50,"#00FFFF"]];
E os próprios arrays são inicializados antes dos níveis:
//
var mapArray = new Array();
var thingsArray = new Array();
var keysArray = new Array();
var startArray = new Array();
var finishArray = new Array();
a função CreateNewWorld () terá que ser alterada adicionando um argumento lá:
function CreateNewWorld(map){
CreateSquares(map,"map");
}
Vamos mudar a chamada para CreateNewWorld () no arquivo menu.js:
button1.onclick = function(){
menu1.style.display = "none";
CreateNewWorld(map);
pawn.x = start[0][0];
pawn.y = start[0][1];
pawn.z = start[0][2];
pawn.rx = start[0][3];
pawn.rx = start[0][4];
CreateSquares(things,"thing");
CreateSquares(keys,"key");
CreateSquares(finish,"finish");
TimerGame = setInterval(repeatFunction,10);
canlock = true;
}
Agora o console apresentará um erro na inicialização. Certo, porque renomeamos as variáveis map, things, keys e finish, agora o javascript não consegue descobrir o que são essas variáveis. Nós os inicializamos novamente em script.js:
//
var map;
var things;
var keys;
var start;
var finish;
E em button1 (em menu.js) atribuímos cópias dos elementos dos arrays mapArray, thingsArray, keysArray e finishArray a essas variáveis (para melhor legibilidade, coloque comentários):
button1.onclick = function(){
//
map = userSlice(mapArray[level]);
things = userSlice(thingsArray[level]);
keys = userSlice(keysArray[level]);
start = userSlice(startArray[level]);
finish = userSlice(finishArray[level]);
//
menu1.style.display = "none";
CreateNewWorld(map);
pawn.x = start[0][0];
pawn.y = start[0][1];
pawn.z = start[0][2];
pawn.rx = start[0][3];
pawn.rx = start[0][4];
CreateSquares(things,"thing");
CreateSquares(keys,"key");
CreateSquares(finish,"finish");
//
TimerGame = setInterval(repeatFunction,10);
canlock = true;
}
Onde userSlice () é a função que copia a matriz:
function userSlice(array){
let NewArray = new Array();
for (let i = 0; i < array.length; i++){
NewArray[i] = new Array();
for (let j = 0; j < array[i].length; j++){
NewArray[i][j] = array[i][j];
}
}
return NewArray;
}
Se simplesmente escrevêssemos, por exemplo, keys = keysArray [nível], então não cópias dos arrays seriam transferidos para as variáveis, mas ponteiros para eles, o que significa que eles mudariam durante o jogo, o que é inaceitável, porque quando você reinicia a chave no o lugar original não existiria mais. Você provavelmente está perguntando por que eu não apenas usei keysArray [level] .slice (), mas inventei minhas próprias funções? Afinal, slice () copia arrays também. Eu tentei fazer isso, mas ele estava copiando a referência para o array, não o array em si, como resultado, a mudança de chaves levava à mudança de keysArray [nível], o que significava que a chave desaparecia na reinicialização. O fato é que a documentação diz que em alguns casos ele percebe arrays como arrays e os copia, em outros ele percebe arrays como objetos e copia apenas ponteiros para eles. Como ele define isso é um mistério para mim,então, se alguém puder me dizer por que o slice () não está funcionando conforme o planejado, ficarei muito grato a ele.
Vamos fazer a transição de níveis. É muito simples. Vamos modificar finishInteract () adicionando as seguintes linhas dentro do else:
level++;
if(level >= 2){
level = 0;
score = 0;
};
Ou seja, o valor do nível é adicionado em 1 e, se todos os níveis forem aprovados (temos 2 deles), os níveis são zerados e os pontos de pontuação são zerados. Isso é difícil de verificar, pois nossos níveis agora não são diferentes. Vamos mudar então mapArray [1]:
mapArray[1] = [
[0,0,1000,0,180,0,2000,200,"#00FF00"],
[0,0,-1000,0,0,0,2000,200,"#00FF00"],
[1000,0,0,0,-90,0,2000,200,"#00FF00"],
[-1000,0,0,0,90,0,2000,200,"#00FF00"],
[0,100,0,90,0,0,2000,2000,"#666666"]
];
Mudamos a cor das paredes. Vamos jogar um jogo. Vemos que depois de passar o primeiro nível (com paredes roxas e vários retângulos), passamos para o segundo (com paredes verdes), e quando passamos o segundo, voltamos para o primeiro. Então, terminamos a transição de níveis. Resta apenas projetar o jogo, mudar as fontes, colorir o mundo e tornar os níveis um pouco mais difíceis. Não alteramos os arquivos index.html e style.css, portanto, verifique os scripts:
script.js
//
var pi = 3.141592;
var deg = pi/180;
// player
function player(x,y,z,rx,ry) {
this.x = x;
this.y = y;
this.z = z;
this.rx = rx;
this.ry = ry;
this.vx = 3;
this.vy = 5;
this.vz = 3;
}
//
var mapArray = new Array();
var thingsArray = new Array();
var keysArray = new Array();
var startArray = new Array();
var finishArray = new Array();
// 1
mapArray[0] = [
[0,0,1000,0,180,0,2000,200,"#F0C0FF"],
[0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
[1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
[-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
[0,0,-300,70,0,0,200,500,"#F000FF"],
[0,-86,-786,90,0,0,200,500,"#F000FF"],
[-500,0,-300,20,0,0,200,500,"#00FF00"],
[0,100,0,90,0,0,2000,2000,"#666666"]
];
thingsArray[0] = [[900,50,-900,0,0,0,50,50,"#FFFF00"],
[-400,50,900,0,0,0,50,50,"#FFFF00"],
[-400,50,-300,0,0,0,50,50,"#FFFF00"]];
keysArray[0] = [[-100,50,600,0,0,0,50,50,"#FF0000"]];
startArray[0] = [[-900,0,-900,0,0]];
finishArray[0] = [[-900,50,900,0,0,0,50,50,"#00FFFF"]];
// 2
mapArray[1] = [
[0,0,1000,0,180,0,2000,200,"#00FF00"],
[0,0,-1000,0,0,0,2000,200,"#00FF00"],
[1000,0,0,0,-90,0,2000,200,"#00FF00"],
[-1000,0,0,0,90,0,2000,200,"#00FF00"],
[0,100,0,90,0,0,2000,2000,"#666666"]
];
thingsArray[1] = [[900,50,-900,0,0,0,50,50,"#FFFF00"],
[-400,50,900,0,0,0,50,50,"#FFFF00"],
[-400,50,-300,0,0,0,50,50,"#FFFF00"]];
keysArray[1] = [[-100,50,600,0,0,0,50,50,"#FF0000"]];
startArray[1] = [[0,0,0,0,0]];
finishArray[1] = [[-900,50,900,0,0,0,50,50,"#00FFFF"]];
//
var map = new Array();
var things = new Array();
var keys = new Array();
var start = new Array();
var finish = new Array();
// ?
var PressBack = 0;
var PressForward = 0;
var PressLeft = 0;
var PressRight = 0;
var PressUp = 0;
var MouseX = 0;
var MouseY = 0;
//
var lock = false;
var onGround = false;
var container = document.getElementById("container");
var world = document.getElementById("world");
var g = 0.1;
var dx = dy = dz = 0;
var canlock = false;
//
document.addEventListener("pointerlockchange", (event)=>{
lock = !lock;
});
//
container.onclick = function(){
if (!lock && canlock) container.requestPointerLock();
};
//
document.addEventListener("keydown", (event) =>{
if (event.key == "a"){
PressLeft = 1;
}
if (event.key == "w"){
PressForward = 1;
}
if (event.key == "d"){
PressRight = 1;
}
if (event.key == "s"){
PressBack = 1;
}
if (event.keyCode == 32){
PressUp = 1;
}
});
//
document.addEventListener("keyup", (event) =>{
if (event.key == "a"){
PressLeft = 0;
}
if (event.key == "w"){
PressForward = 0;
}
if (event.key == "d"){
PressRight = 0;
}
if (event.key == "s"){
PressBack = 0;
}
if (event.keyCode == 32){
PressUp = 0;
}
});
//
document.addEventListener("mousemove", (event)=>{
MouseX = event.movementX;
MouseY = event.movementY;
});
//
var pawn = new player(0,0,0,0,0);
function update(){
//
dx = ((PressRight - PressLeft)*Math.cos(pawn.ry*deg) - (PressForward - PressBack)*Math.sin(pawn.ry*deg))*pawn.vx;
dz = ( -(PressForward - PressBack)*Math.cos(pawn.ry*deg) - (PressRight - PressLeft)*Math.sin(pawn.ry*deg))*pawn.vz;
dy = dy + g;
if (onGround){
dy = 0;
if (PressUp){
dy = - PressUp*pawn.vy;
onGround = false;
}
};
drx = MouseY;
dry = - MouseX;
// :
MouseX = MouseY = 0;
//
collision();
//
pawn.x = pawn.x + dx;
pawn.y = pawn.y + dy;
pawn.z = pawn.z + dz;
// ,
if (lock){
pawn.rx = pawn.rx + drx;
pawn.ry = pawn.ry + dry;
};
// ( )
world.style.transform =
"translateZ(" + (600 - 0) + "px)" +
"rotateX(" + (-pawn.rx) + "deg)" +
"rotateY(" + (-pawn.ry) + "deg)" +
"translate3d(" + (-pawn.x) + "px," + (-pawn.y) + "px," + (-pawn.z) + "px)";
};
function CreateNewWorld(map){
CreateSquares(map,"map");
}
function clearWorld(){
world.innerHTML = "";
}
function collision(){
onGround = false;
for(let i = 0; i < map.length; i++){
//
let x0 = (pawn.x - map[i][0]);
let y0 = (pawn.y - map[i][1]);
let z0 = (pawn.z - map[i][2]);
if ((x0**2 + y0**2 + z0**2 + dx**2 + dy**2 + dz**2) < (map[i][6]**2 + map[i][7]**2)){
let x1 = x0 + dx;
let y1 = y0 + dy;
let z1 = z0 + dz;
let point0 = coorTransform(x0,y0,z0,map[i][3],map[i][4],map[i][5]);
let point1 = coorTransform(x1,y1,z1,map[i][3],map[i][4],map[i][5]);
let normal = coorReTransform(0,0,1,map[i][3],map[i][4],map[i][5]);
//
if (Math.abs(point1[0])<(map[i][6]+98)/2 && Math.abs(point1[1])<(map[i][7]+98)/2 && Math.abs(point1[2]) < 50){
point1[2] = Math.sign(point0[2])*50;
let point2 = coorReTransform(point1[0],point1[1],point1[2],map[i][3],map[i][4],map[i][5]);
let point3 = coorReTransform(point1[0],point1[1],0,map[i][3],map[i][4],map[i][5]);
dx = point2[0] - x0;
dy = point2[1] - y0;
dz = point2[2] - z0;
if (Math.abs(normal[1]) > 0.8){
if (point3[1] > point2[1]) onGround = true;
}
else dy = y1 - y0;
}
}
};
}
function coorTransform(x0,y0,z0,rxc,ryc,rzc){
let x1 = x0;
let y1 = y0*Math.cos(rxc*deg) + z0*Math.sin(rxc*deg);
let z1 = -y0*Math.sin(rxc*deg) + z0*Math.cos(rxc*deg);
let x2 = x1*Math.cos(ryc*deg) - z1*Math.sin(ryc*deg);
let y2 = y1;
let z2 = x1*Math.sin(ryc*deg) + z1*Math.cos(ryc*deg);
let x3 = x2*Math.cos(rzc*deg) + y2*Math.sin(rzc*deg);
let y3 = -x2*Math.sin(rzc*deg) + y2*Math.cos(rzc*deg);
let z3 = z2;
return [x3,y3,z3];
}
function coorReTransform(x3,y3,z3,rxc,ryc,rzc){
let x2 = x3*Math.cos(rzc*deg) - y3*Math.sin(rzc*deg);
let y2 = x3*Math.sin(rzc*deg) + y3*Math.cos(rzc*deg);
let z2 = z3
let x1 = x2*Math.cos(ryc*deg) + z2*Math.sin(ryc*deg);
let y1 = y2;
let z1 = -x2*Math.sin(ryc*deg) + z2*Math.cos(ryc*deg);
let x0 = x1;
let y0 = y1*Math.cos(rxc*deg) - z1*Math.sin(rxc*deg);
let z0 = y1*Math.sin(rxc*deg) + z1*Math.cos(rxc*deg);
return [x0,y0,z0];
};
function CreateSquares(squares,string){
for (let i = 0; i < squares.length; i++){
//
let newElement = document.createElement("div");
newElement.className = string + " square";
newElement.id = string + i;
newElement.style.width = squares[i][6] + "px";
newElement.style.height = squares[i][7] + "px";
newElement.style.background = squares[i][8];
newElement.style.transform = "translate3d(" +
(600 - squares[i][6]/2 + squares[i][0]) + "px," +
(400 - squares[i][7]/2 + squares[i][1]) + "px," +
(squares[i][2]) + "px)" +
"rotateX(" + squares[i][3] + "deg)" +
"rotateY(" + squares[i][4] + "deg)" +
"rotateZ(" + squares[i][5] + "deg)";
// world
world.append(newElement);
}
}
menu.js
//
var menu1 = document.getElementById("menu1");
var menu2 = document.getElementById("menu2");
var menu3 = document.getElementById("menu3");
var button1 = document.getElementById("button1");
var button2 = document.getElementById("button2");
var button3 = document.getElementById("button3");
var button4 = document.getElementById("button4");
var m = [0];
var k = [0];
var f = [0];
var level = 0;
//
button1.onclick = function(){
//
map = userSlice(mapArray[level]);
things = userSlice(thingsArray[level]);
keys = userSlice(keysArray[level]);
start = userSlice(startArray[level]);
finish = userSlice(finishArray[level]);
//
menu1.style.display = "none";
CreateNewWorld(map);
pawn.x = start[0][0];
pawn.y = start[0][1];
pawn.z = start[0][2];
pawn.rx = start[0][3];
pawn.rx = start[0][4];
CreateSquares(things,"thing");
CreateSquares(keys,"key");
CreateSquares(finish,"finish");
//
TimerGame = setInterval(repeatFunction,10);
canlock = true;
}
button2.onclick = function(){
menu1.style.display = "none";
menu2.style.display = "block";
}
button3.onclick = function(){
menu1.style.display = "block";
menu2.style.display = "none";
}
button4.onclick = function(){
menu1.style.display = "block";
menu3.style.display = "none";
}
//
function interact(objects,string,num){
for (i = 0; i < objects.length; i++){
let r = (objects[i][0] - pawn.x)**2 + (objects[i][1] - pawn.y)**2 + (objects[i][2] - pawn.z)**2;
if(r < (objects[i][7]**2)){
document.getElementById(string + i).style.display = "none";
objects[i][0] = 1000000;
objects[i][1] = 1000000;
objects[i][2] = 1000000;
document.getElementById(string + i).style.transform =
"translate3d(1000000px,1000000px,1000000px)";
num[0]++;
};
};
}
//
function finishInteract(){
let r = (finish[0][0] - pawn.x)**2 + (finish[0][1] - pawn.y)**2 + (finish[0][2] - pawn.z)**2;
if(r < (finish[0][7]**2)){
if (k[0] == 0){
console.log(" ");
}
else{
clearWorld();
clearInterval(TimerGame);
document.exitPointerLock();
score = score + m[0];
k[0] = 0;
m[0] = 0;
menu1.style.display = "block";
level++;
if(level >= 2){
level = 0;
score = 0;
};
};
};
};
// ,
function repeatFunction(){
update();
interact(things,"thing",m);
interact(keys,"key",k);
finishInteract();
}
// slice
function userSlice(array){
let NewArray = new Array();
for (let i = 0; i < array.length; i++){
NewArray[i] = new Array();
for (let j = 0; j < array[i].length; j++){
NewArray[i][j] = array[i][j];
}
}
return NewArray;
}
4. Vamos projetar o jogo.
4.1 Mudar os níveis
A construção de níveis é uma atividade muito divertida. Normalmente, isso é feito por indivíduos chamados de designers de níveis. Nosso nível é uma matriz de números que são convertidos por scripts de script.js em um mundo tridimensional. É possível escrever um programa separado para simplificar a criação de mundos, mas agora não faremos isso. Vamos abrir o arquivo script.js e carregar os arrays de labirintos prontos lá:
Matrizes de nível
// 1
mapArray[0] = [
//
[0,0,1000,0,180,0,2000,200,"#F0C0FF"],
[0,0,-1000,0,0,0,2000,200,"#F0C0FF"],
[1000,0,0,0,-90,0,2000,200,"#F0C0FF"],
[-1000,0,0,0,90,0,2000,200,"#F0C0FF"],
[0,100,0,90,0,0,2000,2000,"#EEEEEE"],
//1
[-700,0,-800,0,180,0,600,200,"#F0C0FF"],
[-700,0,-700,0,0,0,600,200,"#F0C0FF"],
[-400,0,-750,0,90,0,100,200,"#F0C0FF"],
//2
[100,0,-800,0,180,0,600,200,"#F0C0FF"],
[50,0,-700,0,0,0,500,200,"#F0C0FF"],
[400,0,-550,0,90,0,500,200,"#F0C0FF"],
[-200,0,-750,0,-90,0,100,200,"#F0C0FF"],
[300,0,-500,0,-90,0,400,200,"#F0C0FF"],
[350,0,-300,0,0,0,100,200,"#F0C0FF"],
//3
[700,0,-800,0,180,0,200,200,"#F0C0FF"],
[700,0,500,0,0,0,200,200,"#F0C0FF"],
[700,0,-150,0,90,0,1100,200,"#F0C0FF"],
[600,0,-150,0,-90,0,1300,200,"#F0C0FF"],
[800,0,-750,0,90,0,100,200,"#F0C0FF"],
[800,0,450,0,90,0,100,200,"#F0C0FF"],
[750,0,400,0,180,0,100,200,"#F0C0FF"],
[750,0,-700,0,0,0,100,200,"#F0C0FF"],
//4
[850,0,-100,0,180,0,300,200,"#F0C0FF"],
[850,0,0,0,0,0,300,200,"#F0C0FF"],
//5
[400,0,300,0,90,0,800,200,"#F0C0FF"],
[300,0,300,0,-90,0,800,200,"#F0C0FF"],
[350,0,-100,0,180,0,100,200,"#F0C0FF"],
//6
[400,0,800,0,0,0,800,200,"#F0C0FF"],
[450,0,700,0,180,0,700,200,"#F0C0FF"],
[800,0,750,0,90,0,100,200,"#F0C0FF"],
[100,0,550,0,90,0,300,200,"#F0C0FF"],
[0,0,650,0,-90,0,300,200,"#F0C0FF"],
[-100,0,500,0,0,0,200,200,"#F0C0FF"],
[-100,0,400,0,180,0,400,200,"#F0C0FF"],
[-200,0,750,0,90,0,500,200,"#F0C0FF"],
[-300,0,700,0,-90,0,600,200,"#F0C0FF"],
//7
[100,0,-250,0,90,0,900,200,"#F0C0FF"],
[0,0,-300,0,-90,0,800,200,"#F0C0FF"],
[-350,0,200,0,0,0,900,200,"#F0C0FF"],
[-350,0,100,0,180,0,700,200,"#F0C0FF"],
[-700,0,-50,0,90,0,300,200,"#F0C0FF"],
[-800,0,0,0,-90,0,400,200,"#F0C0FF"],
[-750,0,-200,0,180,0,100,200,"#F0C0FF"],
//8
[-500,0,600,0,90,0,800,200,"#F0C0FF"],
[-600,0,600,0,-90,0,800,200,"#F0C0FF"],
//9
[-600,0,-500,0,180,0,800,200,"#F0C0FF"],
[-650,0,-400,0,0,0,700,200,"#F0C0FF"],
[-200,0,-300,0,90,0,400,200,"#F0C0FF"],
[-300,0,-300,0,-90,0,200,200,"#F0C0FF"],
[-350,0,-100,0,0,0,300,200,"#F0C0FF"],
[-400,0,-200,0,180,0,200,200,"#F0C0FF"],
[-500,0,-150,0,-90,0,100,200,"#F0C0FF"],
//10
[-900,0,500,0,0,0,200,200,"#F0C0FF"],
[-900,0,400,0,180,0,200,200,"#F0C0FF"],
[-800,0,450,0,90,0,100,200,"#F0C0FF"]
];
thingsArray[0] = [[900,50,-900,0,0,0,50,50,"#FFFF00"],
[-400,50,900,0,0,0,50,50,"#FFFF00"],
[-400,50,-300,0,0,0,50,50,"#FFFF00"]];
keysArray[0] = [[-100,50,600,0,0,0,50,50,"#FF0000"]];
startArray[0] = [[-900,0,-900,0,0]];
finishArray[0] = [[-900,50,900,0,0,0,50,50,"#00FFFF"]];
// 2
mapArray[1] = [
//
[0,0,1200,0,180,0,2400,200,"#C0FFE0"],
[0,0,-1200,0,0,0,2400,200,"#C0FFE0"],
[1200,0,0,0,-90,0,2400,200,"#C0FFE0"],
[-1200,0,0,0,90,0,2400,200,"#C0FFE0"],
[0,100,0,90,0,0,2400,2400,"#EEEEEE"],
//1
[1100,0,-800,0,180,0,200,200,"#C0FFE0"],
[1000,0,-900,0,90,0,200,200,"#C0FFE0"],
[850,0,-1000,0,180,0,300,200,"#C0FFE0"],
[700,0,-950,0,-90,0,100,200,"#C0FFE0"],
[800,0,-900,0,0,0,200,200,"#C0FFE0"],
[900,0,-700,0,-90,0,400,200,"#C0FFE0"],
[750,0,-500,0,180,0,300,200,"#C0FFE0"],
[600,0,-450,0,-90,0,100,200,"#C0FFE0"],
[800,0,-400,0,0,0,400,200,"#C0FFE0"],
[1000,0,-550,0,90,0,300,200,"#C0FFE0"],
[1100,0,-700,0,0,0,200,200,"#C0FFE0"],
//2
[800,0,-200,0,180,0,800,200,"#C0FFE0"],
[400,0,-300,0,90,0,200,200,"#C0FFE0"],
[300,0,-400,0,180,0,200,200,"#C0FFE0"],
[200,0,-700,0,90,0,600,200,"#C0FFE0"],
[50,0,-1000,0,180,0,300,200,"#C0FFE0"],
[-100,0,-950,0,-90,0,100,200,"#C0FFE0"],
[0,0,-900,0,0,0,200,200,"#C0FFE0"],
[100,0,-600,0,-90,0,600,200,"#C0FFE0"],
[200,0,-300,0,0,0,200,200,"#C0FFE0"],
[300,0,-200,0,-90,0,200,200,"#C0FFE0"],
[750,0,-100,0,0,0,900,200,"#C0FFE0"],
//3
[500,0,-950,0,90,0,500,200,"#C0FFE0"],
[450,0,-700,0,0,0,100,200,"#C0FFE0"],
[400,0,-950,0,-90,0,500,200,"#C0FFE0"],
//4
[-700,0,-600,0,0,0,1000,200,"#C0FFE0"],
[-200,0,-500,0,-90,0,200,200,"#C0FFE0"],
[-300,0,-400,0,180,0,200,200,"#C0FFE0"],
[-400,0,-250,0,-90,0,300,200,"#C0FFE0"],
[-350,0,-100,0,0,0,100,200,"#C0FFE0"],
[-300,0,-200,0,90,0,200,200,"#C0FFE0"],
[-200,0,-300,0,0,0,200,200,"#C0FFE0"],
[-100,0,-500,0,90,0,400,200,"#C0FFE0"],
[-650,0,-700,0,180,0,1100,200,"#C0FFE0"],
//5
[-300,0,-850,0,90,0,300,200,"#C0FFE0"],
[-350,0,-1000,0,180,0,100,200,"#C0FFE0"],
[-400,0,-850,0,-90,0,300,200,"#C0FFE0"],
//6
[-600,0,-1050,0,90,0,300,200,"#C0FFE0"],
[-650,0,-900,0,0,0,100,200,"#C0FFE0"],
[-700,0,-1050,0,-90,0,300,200,"#C0FFE0"],
//7
[-900,0,-850,0,90,0,300,200,"#C0FFE0"],
[-950,0,-1000,0,180,0,100,200,"#C0FFE0"],
[-1000,0,-850,0,-90,0,300,200,"#C0FFE0"],
//8
[-600,0,-250,0,90,0,700,200,"#C0FFE0"],
[-650,0,100,0,0,0,100,200,"#C0FFE0"],
[-700,0,-250,0,-90,0,700,200,"#C0FFE0"],
//9
[-900,0,-150,0,90,0,900,200,"#C0FFE0"],
[-500,0,300,0,180,0,800,200,"#C0FFE0"],
[-100,0,650,0,90,0,700,200,"#C0FFE0"],
[-300,0,1000,0,0,0,400,200,"#C0FFE0"],
[-500,0,950,0,-90,0,100,200,"#C0FFE0"],
[-350,0,900,0,180,0,300,200,"#C0FFE0"],
[-200,0,650,0,-90,0,500,200,"#C0FFE0"],
[-600,0,400,0,0,0,800,200,"#C0FFE0"],
[-1000,0,-100,0,-90,0,1000,200,"#C0FFE0"],
//10
[-300,0,200,0,90,0,200,200,"#C0FFE0"],
[-350,0,100,0,180,0,100,200,"#C0FFE0"],
[-400,0,200,0,-90,0,200,200,"#C0FFE0"],
//11
[-800,0,600,0,180,0,800,200,"#C0FFE0"],
[-400,0,650,0,90,0,100,200,"#C0FFE0"],
[-800,0,700,0,0,0,800,200,"#C0FFE0"],
//12
[-700,0,1050,0,90,0,300,200,"#C0FFE0"],
[-850,0,900,0,180,0,300,200,"#C0FFE0"],
[-1000,0,950,0,-90,0,100,200,"#C0FFE0"],
[-900,0,1000,0,0,0,200,200,"#C0FFE0"],
[-800,0,1100,0,-90,0,200,200,"#C0FFE0"],
//13
[1050,0,700,0,180,0,300,200,"#C0FFE0"],
[900,0,800,0,-90,0,200,200,"#C0FFE0"],
[550,0,900,0,180,0,700,200,"#C0FFE0"],
[200,0,650,0,90,0,500,200,"#C0FFE0"],
[300,0,400,0,0,0,200,200,"#C0FFE0"],
[400,0,300,0,90,0,200,200,"#C0FFE0"],
[550,0,200,0,0,0,300,200,"#C0FFE0"],
[700,0,150,0,90,0,100,200,"#C0FFE0"],
[500,0,100,0,180,0,400,200,"#C0FFE0"],
[300,0,200,0,-90,0,200,200,"#C0FFE0"],
[200,0,300,0,180,0,200,200,"#C0FFE0"],
[100,0,650,0,-90,0,700,200,"#C0FFE0"],
[550,0,1000,0,0,0,900,200,"#C0FFE0"],
[1000,0,900,0,90,0,200,200,"#C0FFE0"],
[1100,0,800,0,0,0,200,200,"#C0FFE0"],
//14
[700,0,700,0,90,0,400,200,"#C0FFE0"],
[850,0,500,0,0,0,300,200,"#C0FFE0"],
[1000,0,300,0,90,0,400,200,"#C0FFE0"],
[950,0,100,0,180,0,100,200,"#C0FFE0"],
[900,0,250,0,-90,0,300,200,"#C0FFE0"],
[750,0,400,0,180,0,300,200,"#C0FFE0"],
[600,0,650,0,-90,0,500,200,"#C0FFE0"],
//15
[500,0,600,0,180,0,200,200,"#C0FFE0"],
[400,0,650,0,-90,0,100,200,"#C0FFE0"],
[500,0,700,0,0,0,200,200,"#C0FFE0"]
];
thingsArray[1] = [[1100,50,900,0,0,0,50,50,"#FFFF00"],
[500,50,800,0,0,0,50,50,"#FFFF00"],
[-800,50,-500,0,0,0,50,50,"#FFFF00"],
[-900,50,1100,0,0,0,50,50,"#FFFF00"],
[-1100,50,-800,0,0,0,50,50,"#FFFF00"]
];
keysArray[1] = [[1100,50,-900,0,0,0,50,50,"#FF0000"]];
startArray[1] = [[0,0,0,0,0]];
finishArray[1] = [[-1100,50,-500,0,0,0,50,50,"#00FFFF"]];
Agora podemos jogar o jogo. Como resultado, os níveis são assim:
É extremamente difícil navegar em um mundo assim. Além disso, o movimento ao longo das paredes contém bugs, já que o jogador pode ficar preso nos cantos das paredes. Vamos consertar isso em colisão (), substituindo 98 por 90:
//
if (Math.abs(point1[0])<(map[i][6]+90)/2 && Math.abs(point1[1])<(map[i][7]+90)/2 && Math.abs(point1[2]) < 50){
4.2 Adicionar iluminação estática
Para facilitar a navegação, implementamos iluminação solar estática (sem sombras). Vamos adicionar um vetor de luz solar:
var sun = [0.48,0.8,0.36];
Como criar iluminação? Observe a imagem:
Se o vetor sol for exatamente oposto ao vetor n, a iluminação é maximizada. A intensidade da luz depende do ângulo de incidência da luz na superfície. Se um raio de luz cair paralelo ao plano ou do lado oposto, o plano não será iluminado. O ângulo de incidência pode ser calculado usando o produto escalar n * sol: se for negativo, a iluminação depende do módulo do produto escalar, e se for positivo, não há iluminação. Criaremos a iluminação das superfícies ao gerar o mundo, ou seja, em CreateNewWorld (). E como só existe a função CreateSquare (), vamos aplicar a iluminação lá. Mas provavelmente só aplicaremos anunciação ao mundo, mas não às coisas, então adicionaremos o argumento de iluminação lá e alteraremos o próprio CreateSquare ():
function CreateSquares(squares,string,havelight){
for (let i = 0; i < squares.length; i++){
//
let newElement = document.createElement("div");
newElement.className = string + " square";
newElement.id = string + i;
newElement.style.width = squares[i][6] + "px";
newElement.style.height = squares[i][7] + "px";
if (havelight){
let normal = coorReTransform(0,0,1,squares[i][3],squares[i][4],squares[i][5]);
let light = -(normal[0]*sun[0] + normal[1]*sun[1] + normal[2]*sun[2]);
if (light < 0){
light = 0;
};
newElement.style.background = "linear-gradient(rgba(0,0,0," + (0.2 - light*0.2) + "),rgba(0,0,0," + (0.2 - light*0.2) + ")), " + squares[i][8];
}
else{
newElement.style.background = squares[i][8];
}
newElement.style.transform = "translate3d(" +
(600 - squares[i][6]/2 + squares[i][0]) + "px," +
(400 - squares[i][7]/2 + squares[i][1]) + "px," +
(squares[i][2]) + "px)" +
"rotateX(" + squares[i][3] + "deg)" +
"rotateY(" + squares[i][4] + "deg)" +
"rotateZ(" + squares[i][5] + "deg)";
// world
world.append(newElement);
}
}
Vamos ligar a iluminação ao gerar o mundo em CreateNewWorld ():
function CreateNewWorld(map){
CreateSquares(map,"map",true);
}
E adicione a desativação da iluminação para os itens em button1.onclick (em CreateSquares, o último parâmetro para eles é falso):
//
menu1.style.display = "none";
CreateNewWorld(map);
pawn.x = start[0][0];
pawn.y = start[0][1];
pawn.z = start[0][2];
pawn.rx = start[0][3];
pawn.rx = start[0][4];
CreateSquares(things,"thing",false);
CreateSquares(keys,"key",false);
CreateSquares(finish,"finish",false);
Vamos começar o jogo e notar que a iluminação se tornou mais realista e é muito mais fácil navegar no espaço:
Adicione um céu azul. Vamos definir o plano de fundo para #container em style.css:
background-color:#C0FFFF;
O céu ficou azul:
nós projetamos os níveis. Mas ainda é difícil encontrar itens, uma vez que são estáticos, e o jogador é intuitivamente difícil de entender que eles podem ser coletados.
4.3 Adicionar rotação e luz aos objetos
No menu.js, vamos criar uma função de rotação separada:
function rotate(objects,string,wy){
for (i = 0; i < objects.length; i++){
objects[i][4] = objects[i][4] + wy;
document.getElementById(string + i).style.transform = "translate3d(" +
(600 - objects[i][6]/2 + objects[i][0]) + "px," +
(400 - objects[i][7]/2 + objects[i][1]) + "px," +
(objects[i][2]) + "px)" +
"rotateX(" + objects[i][3] + "deg)" +
"rotateY(" + objects[i][4] + "deg)" +
"rotateZ(" + objects[i][5] + "deg)";
};
}
E vamos chamá-lo de repeatFunction ():
function repeatFunction(){
update();
interact(things,"thing",m);
interact(keys,"key",k);
rotate(things,"thing",0.5);
rotate(keys,"key",0.5);
rotate(finish,"finish",0.5);
finishInteract();
}
É verdade que a função de rotação pode ser usada não apenas para girar objetos, mas também para movê-los. Então, os objetos estão girando. Mas se tornarmos esses objetos luminosos, geralmente será super. Vamos definir sombras coloridas para eles em style.css:
.thing{
box-shadow: 0 0 10px #FFFF00;
}
.key{
box-shadow: 0 0 10px #FF0000;
}
.finish{
box-shadow: 0 0 10px #00FFFF;
}
Agora o jogador entende com certeza que esses itens podem interagir.
4.4 Adicionar widgets
Normalmente, os widgets mostram a pontuação, a integridade e outros dados numéricos necessários. Aqui eles mostrarão o número de moedas coletadas (quadrados amarelos) e chaves (quadrados vermelhos), e você pode alterá-los do javascript. Primeiro, vamos adicionar novos elementos ao html:
<div id="container">
<div id="world"></div>
<div id="pawn"></div>
<div class = "widget" id = "widget1"></div>
<div class = "widget" id = "widget2"></div>
<div class = "widget" id = "widget3"></div>
…
No menu.js, vamos vincular variáveis a eles:
var widget1 = document.getElementById("widget1");
var widget2 = document.getElementById("widget2");
var widget3 = document.getElementById("widget3");
E dentro de button1.onclick () adicione texto a eles:
widget1.innerHTML = "<p style='font-size:30px'>: 0 0" </p>";
widget2.innerHTML = "<p style='font-size:30px'>:0</p>";
widget3.innerHTML = "<p style='font-size:40px'> !</p>";
Vamos estilizá-los em style.css ():
/* */
.widget{
display:none;
position:absolute;
background-color:#FFF;
opacity:0.8;
z-index:300;
}
#widget1{
top:0px;
left:0px;
width:300px;
height:100px;
}
#widget2{
top:0px;
right:0px;
width:300px;
height:100px;
}
#widget3{
bottom:0px;
left:0px;
width:500px;
height:200px;
}
Eles são inicialmente invisíveis. Vamos tornar os 2 primeiros widgets visíveis ao iniciar o nível dentro de button1.onclick:
//
widget1.style.display = "block";
widget2.style.display = "block";
widget1.innerHTML = "<p style='font-size:30px'>: 0 " + things.length + " </p>";
widget2.innerHTML = "<p style='font-size:30px'>:0</p>";
widget3.innerHTML = "<p style='font-size:40px'> !</p>";
Existem widgets, mas nada acontece ao interagir com objetos. Vamos mudar os rótulos dos widgets ao interagir com as funções de interação (dentro de if (r <(objetos [i] [7] ** 2)) {...}):
widget1.innerHTML = "<p style='font-size:30px'>: " + m[0] + " " + things.length + " </p>";
widget2.innerHTML = "<p style='font-size:30px'>: " + k[0] + "</p>";
Agora, ao pegar moedas e uma chave, as informações nos widgets mudam. Mas quando o jogo termina, os widgets não estão ocultos. Esconda-os no final do jogo, adicionando as seguintes linhas a finishInteract () dentro de else:
widget1.style.display = "none";
widget2.style.display = "nenhum";
widget3.style.display = "nenhum";
Os widgets estão ocultos. Resta configurar um widget que pede a você para pegar uma chave se você chegar à linha de chegada sem ela. Em finishInteract (), em vez de console.log ("encontre a chave"), insira as seguintes linhas:
widget3.style.display = "block";
setTimeout(() => widget3.style.display = "none",5000);
Se a tentativa de finalizar o jogo não for bem-sucedida, recebemos uma mensagem que desaparece após 5 segundos. Nosso jogo agora se parece com este:
4.5 Vamos formatar o texto.
Vamos criar uma pasta Fonts na pasta de arquivos. Baixe o arquivo font1.woff aqui e cole-o em Fonts. Adicione estilos de texto a style.css:
/* */
p{
margin:0px;
font-size:60px;
position:absolute;
display:block;
top:50%;
left:50%;
transform:translate(-50%,-50%);
user-select:none;
font-family:fontlab;
}
@font-face{
font-family:fontlab;
src:url("Fonts/font1.woff");
}
O menu e o jogo mudaram:
4.6 Adicionar sons.
Baixe o arquivo Sounds.zip aqui . Crie uma pasta Sons na pasta do projeto e insira os sons lá (eles estão no formato mp3). Vamos fazer referências variáveis a estes sons:
//
var clickSound = new Audio;
clickSound.src = "Sounds/click.mp3";
var keySound = new Audio;
keySound.src = "Sounds/key.mp3";
var mistakeSound = new Audio;
mistakeSound.src = "Sounds/mistake.mp3";
var thingSound = new Audio;
thingSound.src = "Sounds/thing.mp3";
var winSound = new Audio;
winSound.src = "Sounds/win.mp3";
Na função de interação, adicione um argumento ao arquivo de som e reproduza o som (soundObject.play ()):
function interact(objects,string,num,soundObject){
for (i = 0; i < objects.length; i++){
let r = (objects[i][0] - pawn.x)**2 + (objects[i][1] - pawn.y)**2 + (objects[i][2] - pawn.z)**2;
if(r < (objects[i][7]**2)){
soundObject.play();
document.getElementById(string + i).style.display = "none";
objects[i][0] = 1000000;
objects[i][1] = 1000000;
objects[i][2] = 1000000;
document.getElementById(string + i).style.transform =
"translate3d(1000000px,1000000px,1000000px)";
num[0]++;
widget1.innerHTML = "<p style='font-size:30px'>: " + m[0] + " " + things.length + " </p>";
widget2.innerHTML = "<p style='font-size:30px'>: " + k[0] + "</p>";
};
};
}
Em repeatFunction (), altere as chamadas para esta função de acordo:
interact(things,"thing",m,thingSound);
interact(keys,"key",k,keySound);
E em finishInteract (), adicione os sons errorSound e winSound:
function finishInteract(){
let r = (finish[0][0] - pawn.x)**2 + (finish[0][1] - pawn.y)**2 + (finish[0][2] - pawn.z)**2;
if(r < (finish[0][7]**2)){
if (k[0] == 0){
widget3.style.display = "block";
setTimeout(() => widget3.style.display = "none",5000);
mistakeSound.play();
}
else{
clearWorld();
clearInterval(TimerGame);
document.exitPointerLock();
score = score + m[0];
k[0] = 0;
m[0] = 0;
level++;
menu1.style.display = "block";
widget1.style.display = "none";
widget2.style.display = "none";
widget3.style.display = "none";
winSound.play();
if(level >= 2){
level = 0;
score = 0;
};
};
};
};
Quando você clica em qualquer botão do menu, reproduziremos o som clickSound:
button1.onclick = function(){
clickSound.play();
...
}
button2.onclick = function(){
clickSound.play();
menu1.style.display = "none";
menu2.style.display = "block";
}
button3.onclick = function(){
clickSound.play();
menu1.style.display = "block";
menu2.style.display = "none";
}
button4.onclick = function(){
clickSound.play();
menu1.style.display = "block";
menu3.style.display = "none";
}
O jogo ficou mais brilhante. Resta personalizar a saída dos resultados após passar em todos os níveis:
4.7 Saída de resultados.
Em menu.js em finishInteract () dentro de if (level> = 2) {…} adicione as seguintes linhas:
if(level >= 2){
menu1.style.display = "none";
menu3.style.display = "block";
document.getElementById("result").innerHTML = " " + score + " ";
level = 0;
score = 0;
};
Vemos o número de pontos marcados após completar todos os níveis.
A propósito, não vamos esquecer de adicionar a linha à mesma função:
canlock = false;
E:
button1.innerHTML = "<p></p>";
e
button1.innerHTML = "<p> </p>";
Como um resultado:
function finishInteract(){
let r = (finish[0][0] - pawn.x)**2 + (finish[0][1] - pawn.y)**2 + (finish[0][2] - pawn.z)**2;
if(r < (finish[0][7]**2)){
if (k[0] == 0){
…
}
else{
…
canlock = false;
button1.innerHTML = "<p></p>";
if(level >= 2){
menu1.style.display = "none";
menu3.style.display = "block";
document.getElementById("result").innerHTML = " " + score + " ";
level = 0;
score = 0;
button1.innerHTML = "<p> </p>";
};
};
};
};
Agora, o botão de início do jogo muda dependendo da passagem dos níveis. Além disso, mova “container” para o centro da janela, adicionando as seguintes linhas a seus estilos:
top:50%;
left:50%;
transform: translate(-50%,-50%);
E remova os travessões do corpo:
body{
margin:0px;
}
Então, nós escrevemos completamente um jogo de labirinto 3D baseado em navegador. Graças a ela, chamamos a atenção para alguns aspectos da linguagem javascript, conhecendo funções que talvez você não tenha ouvido antes. E o mais importante, mostramos que não é tão difícil fazer brinquedos simples para o navegador, mesmo em código puro. Você pode baixar o código-fonte completo aqui (sources.zip) . Os próprios scripts podem ser melhorados significativamente adicionando-se bibliotecas diferentes, escrevendo novos construtores ou fazendo outra coisa.
Obrigado pela atenção!