js 游戏编程:(平滑跟随算法 / 碰撞检测算法) 贪吃蛇
相信大家都用 c 语言写过贪吃蛇吧!今天让我们来试试 js 写的贪吃蛇!
全程无参考代码,完全自己设计和编写的代码哦~
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
<style>
@keyframes rot {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
span {
position: absolute;
border: 1px solid;
color: white;
font-size: larger;
background-color: rgb(3, 108, 228);
animation: rot 2s linear infinite;
text-shadow: 1px 1px 2px red, 0 0 1em blue, 0 0 0.2em blue;
}
.food {
position: absolute;
width: 50px;
height: 50px;
border-radius: 50%;
border: 1px solid;
color: white;
background-color: rgb(5, 230, 54);
}
body {
background-image: url(https://c-ssl.dtstatic.com/uploads/blog/202204/03/20220403215603_6fb22.thumb.1000_0.png);
background-size: contain;
}
button {
position: absolute;
width: 50px;
height: 50px;
background: #333;
color: #fff;
}
#left {
left: 50px;
bottom: 100px;
border-radius: 50%;
}
#right {
left: 150px;
bottom: 100px;
border-radius: 50%;
}
#up {
left: 100px;
bottom: 150px;
border-radius: 50%;
}
#down {
left: 100px;
bottom: 50px;
border-radius: 50%;
}
</style>
<p style="text-align: center;font-size: larger;background-color: rgb(248, 166, 166);">tip: 点击开始按钮之后,按一个方向键,蛇蛇才能移动 *^_^*</p>
<div id="food"></div>
<div id="body">
<span id="head" style="left:10px;top:10px;">原</span>
</div>
<button id="game" onclick="game()">开始游戏</button>
<button id="left" onclick="(() => flg = 0)()">左</button>
<button id="right" onclick="(() => flg = 2)()">右</button>
<button id="up" onclick="(() => flg = 1)()">上</button>
<button id="down" onclick="(() => flg = 3)()">下</button>
<script>
const names = ['原','神','启','动']
let startGame = document.getElementById("game");
let game = () => {
startGame.style.display = 'none';
ctlMove(head);
// 创造墙
// addWall();
//ctlAngMove(head);
}
let initGame = () => {
startGame.style.display = 'block';
}
// 游戏结束
// testBigger();
let endGame = ()=>{
alert("你已撞墙,游戏结束!\n 得分:" + (size*100) + "分");
flg = 4; // 重置方向
clearBody();
clearFoods();
initGame();
}
// 获取 当前屏幕宽高
var width = document.body.clientWidth || window.innerWidth || document.documentElement.clientWidth || screen.width || document.body.scrollWidth || 0;
var height = document.body.clientHeight || window.innerHeight || document.documentElement.clientHeight || screen.height || document.body.scrollHeight || 0;
// 在屏幕随机位置生成物品
let rdmLeft = () => 15 + Math.random() * (width - 30);
let rdmTop = () => 15 + Math.random() * (height - 30);
// 食物
let foodlist = []
let foods = document.getElementById("food");
// 随机生成食物
let gfCdtime = false; // 是否冷却时间
let geneFood = () => {
if(gfCdtime) return;
// 配置食物的属性
let food = document.createElement('div');
food.className = 'food';
food.style.left = rdmLeft();
food.style.top = rdmTop();
// 存储和页面加载食物
foodlist.push(food);
foods.append(food);
// 进入冷却时间(加锁)
gfCdtime = true;
setTimeout(()=>{
gfCdtime = false;
}, 5000);
}
// 清空食物列表
let clearFoods = ()=>{
for(var i = 0; i < foodlist.length; i++){
foods.remove(foodlist[i][0]);
} foodlist = [];
foods = document.createElement('div');
foods.id = 'food';
document.body.appendChild(foods);
}
// 头和身体
let head = document.getElementById("head");
let body = document.getElementById("body");
// 身体列表
let bodylist = [head];
let size = 0;
// 生成随机单色
let rdmSingClr = () => parseInt(Math.random() * 254);
// 生成随机颜色
let rdmClr = () =>
`rgb(${rdmSingClr()}, ${rdmSingClr()}, ${rdmSingClr()})`;
// 扩大身体算法
let bigger = () => {
let e = document.createElement('span');
e.innerText = names[(size + 1) % 4];
e.style.background = rdmClr(); // 生成随机身体颜色
e.style.left = bodylist[size].style.left;
e.style.top = bodylist[size ++].style.top;
bodylist.push(e);
body.append(e);
}
// 身体扩大测试
let testBigger = () => {
setInterval(()=>{
bigger();
// console.log(bodylist)
}, 1000);
}
// 吃食物身体扩大
let eat = (e) => {
// 检测是否吃到
let eatnum = collisionEat(e);
// 扩大身体
for(var i = 0; i < eatnum; i++) bigger();
}
// 清空身体
let clearBody = ()=>{
// 清空头节点的位置
head.style = '';
// 在页面上移除身体
for(var i = 0; i < bodylist.length; i++){
// console.log(bodylist[i][0]);
body.remove(bodylist[i]);
} // 清空身体列表
head = document.createElement('span');
head.id = 'head';
head.innerHTML = names[0];
head.style = 'left:10px;top:10px;';
body = document.createElement('div');
body.id = 'body';
document.body.appendChild(body);
body.appendChild(head);
bodylist = [head];
size = 0;
}
/***
* todo 1. 4 方向移动算法
*/
// 方向标记位
let flg = 4;
// 事件 ---- 读取方向按键
////
document.onkeydown =
function ev1(evt){
var e = evt || event;
var keyCode = e.keyCode || e.which;
switch(keyCode){
case 37 : flg = 0; break;
case 38 : flg = 1; break;
case 39 : flg = 2; break;
case 40 : flg = 3; break;
}
}
// 可控制方向的移动算法
let ctlMove = (e)=>{
let posx = 1;
let posy = 1;
let stopMove = setInterval(() => {
// 随机生成食物
geneFood();
// 检测当前吃到多少食物
eat(head);
// 检测当前是否撞墙:
if(collisionDeath(head)) {
clearInterval(stopMove);
endGame();
return;
}
// 移动 头部
if(flg == 0){
e.style.left = posx -= 4 + (size >> 1);
//console.log(flg,posx)
} else if(flg == 1){
e.style.top = posy -= 4 + (size >> 1);
//console.log(flg)
} else if(flg == 2){
e.style.left = posx += 4 + (size >> 1);
//console.log(flg,posx)
} else if(flg == 3){
// debugger
e.style.top = posy += 4 + (size >> 1);
}
// 平滑移动 身体
smoMoveBody();
}, 25);
}
/***
* todo 2. 可控制角度的移动算法
*/
// 求 当前角度 对应的 x / y 方向上的偏移比例
let angle = 0;
let sin = 0;
let cos = 0;
let updateAngle = () => {
sin = Math.sin(angle);
cos = Math.cos(angle);
}
updateAngle();
// 可控制角度的移动算法
let ctlAngMove = (e) => {
let x = 1;
let y = 1;
setInterval(() => {
e.style.left = x += cos * 4;
e.style.top = y -= sin * 4;
smoMoveBody();
}, 25);
}
// 读取方向按键 改变角度
//// document.onkeydown =
function ev2(evt){
var e = evt || event;
var keyCode = e.keyCode || e.which;
switch(keyCode){
case 37 : angle += Math.PI / 12; break;
case 39 : angle -= Math.PI / 12; break;
} updateAngle();
}
// 平滑移动算法
let smoMoveBody = () => {
var prex = parseFloat(bodylist[0].style.left);
var prey = parseFloat(bodylist[0].style.top);
for(var i = 1; i <= size; i++){
var l = bodylist[i].style.left;
var t = bodylist[i].style.top;
var curx = parseFloat(l === "" ? 0 : l);
var cury = parseFloat(t === "" ? 0 : t);
bodylist[i].style.left = prex = (prex - curx) / 6 + curx;
bodylist[i].style.top = prey = (prey - cury) / 6 + cury;
//var len = Math.sqrt(x * x + y * y);
//bodylist[i].style.left = x / len * 5 + curx;
//bodylist[i].style.top = y / len * 5 + cury;
}
}
// 检测是否碰到所有食物
let collisionEat = (e) => {
var arr = []
for(var i = 0; i < foodlist.length; i++){
if(collisionFood(foodlist[i], e, 2000)) arr.push(i);
}
for(var i = 0; i < arr.length; i++){
console.log(arr[i]);
if(arr[i] === undefined || null || NaN) continue;
var remvdFood = foodlist.splice(arr[i], 1);
console.log(remvdFood[0]);
foods.removeChild(remvdFood[0]);
}
return arr.length;
}
// 检测是否碰到一个食物
let collisionFood = (src, tar, dist/* 两点间距的平方 */) => {
var dx = parseFloat(src.style.left) - parseFloat(tar.style.left);
var dy = parseFloat(src.style.top) - parseFloat(tar.style.top);
if(dx * dx + dy * dy < dist) return true;
return false;
}
// 检测是否撞到边界,死亡
let collisionDeath = (src) => {
if(src.style.left === "" || src.style.top === "") return;
var dx = parseFloat(src.style.left);
var dy = parseFloat(src.style.top);
if(dx < 0 || dx > width || dy < 0 || dy > height) {
console.log(dx + " " + dy);
return true;
}
return false;;
}
// 增加边界墙
// tip: 目前这个方法不行
let addWall = () => {
let wall = document.createElement('div');
wall.style = `color: black; width: 3rem; height: 95%; position: abstract;left: ${width - 15}`;
document.body.appendChild(wall);
console.log(wall)
}
</script>