1、写一个简单的贪吃蛇
需求分析
搭建环境
使用@webpack-cli/generators
搭建一个ts环境
@webpack-cli/generators
//add npm
npm init
//add webpack
yarn add -D @webpack-cli/generators webpack webpack-cli
//webpack init 新版本可以不推荐使用webpack-cli,根据返回提示来操作就行了
yarn webpack init
// 启动项目
yarn serve
根据选项,选择ts
项目结构
在src中创建一个tanchishe.ts
的文件,在index.ts
中调用
//index.ts
import Tanchishe from './tanchishe';
Tanchishe;
//tanchishe.ts
import PIXI from 'pixi.js';
class Core {
}
const Tanchishe = new Core();
export default Tanchishe;
根据思维导图的逻辑来
i
mport {
Application,
Container,
Sprite,
Loader,
Graphics,
Text,
TextStyle,
ParticleContainer,
Texture,
TilingSprite,
} from "pixi.js";
import { keyboard } from "./keyboard";
import Apple from "./assets/apple.jpg";
interface CoreProps {
element: string;
width: string | number;
height: string | number;
isLoader?: boolean;
}
interface snakeAxisProps {
x?: number;
y?: number;
width?: number;
height?: number;
}
interface directionProps {
upIsDown: boolean;
leftIsDown: boolean;
downIsDown: boolean;
rightIsDown: boolean;
}
class Core {
element: Element;
width: string | number;
height: string | number;
renderer!: any;
loader!: any;
isLoader?: boolean;
isVisible: boolean;
speed: snakeAxisProps;
snake?: any;
snakeArr?: any[];
isInitSnake: boolean;
apple?: any;
wallArr?: any;
state: any;
score: number;
direction: directionProps;
timer:null | NodeJS.Timer
constructor(option: CoreProps) {
const { element, width, height, isLoader = false } = option;
this.element = document.querySelector(`#${element}`);
this.width = width;
this.height = height;
this.isLoader = isLoader;
this.snake = null;
this.snakeArr = [];
this.apple = null;
this.score = 0;
this.isVisible = false;
this.renderer = null;
this.isInitSnake = true;
this.direction = {
upIsDown: true,
leftIsDown: true,
downIsDown: true,
rightIsDown: true,
};
this.timer = null;
this.speed = {};
this.init();
}
init() {
console.log("init");
this.apple = null;
this.wallArr = null;
this.renderer = new Application({
width: this.width as number,
height: this.height as number,
backgroundColor: 0xffffff,
});
this.getScore();
this.createRender();
}
loaderImg(option: {
imgUrl: string;
loaded: (loader: any, resources: any) => void;
}) {
const { imgUrl, loaded } = option;
this.loader = Loader.shared;
this.loader.add("apple", imgUrl).load(loaded);
}
createRender() {
this.renderer.stage.removeChildren();
this.element?.appendChild(this.renderer.view);
console.log(this.isVisible);
if (this.isVisible) {
this.createWalls();
this.createSnake();
this.createApple();
//移动蛇
this.timer = setInterval(()=>{
this.snakeMove();
},100)
} else {
this.startPage();
}
}
getScore() {
const body = document.body;
const scoreWrapper = document.createElement("div");
scoreWrapper.innerHTML = `
<span>分数:</span><span id="Score">0</span>
`;
body.appendChild(scoreWrapper);
}
setScore() {
this.score++;
const Score = document.querySelector("#Score");
Score.innerHTML = `${this.score}`;
}
over() {
this.renderer.stage.removeChildren();
clearInterval(this.timer);
this.endPage();
}
endPage() {
const { width, height, renderer, score } = this;
const graphics = new Graphics();
graphics.beginFill(0x000000);
graphics.drawRect(0, 0, width as number, height as number);
graphics.endFill();
renderer.stage.addChild(graphics);
const endText = new Text(
`游戏结束,你的分数为:${score}`,
new TextStyle({
fill: 0xcccccc,
})
);
renderer.stage.addChild(endText);
}
startPage() {
const { width, height, renderer } = this;
const startContainer = new Container();
const graphics = new Graphics();
graphics.beginFill(0x000000);
graphics.drawRect(0, 0, width as number, height as number);
graphics.endFill();
startContainer.addChild(graphics);
const style = new TextStyle({
fontFamily: "Arial",
fontSize: 36,
fill: "white",
stroke: "#ff3300",
strokeThickness: 4,
dropShadow: true,
dropShadowColor: "#cccccc",
dropShadowBlur: 4,
dropShadowAngle: Math.PI / 6,
dropShadowDistance: 6,
});
const message = new Text("贪吃蛇(乞丐版)", style);
message.position.set((width as number) / 3, (height as number) / 4);
startContainer.addChild(message);
const border = new Graphics();
border.beginFill(0xcccccc);
border.lineStyle({ color: 0xffffff, width: 4, alignment: 0 });
border.drawRect(0, 0, 120, 60);
border.endFill();
border.position.set((this.width as number) / 2.5, (height as number) / 2);
startContainer.addChild(border);
const button = new Text("游戏开始");
border.interactive = true;
border.on("pointerdown", (e) => {
this.isVisible = true;
this.createRender();
});
button.position.set((width as number) / 2.45, (height as number) / 1.9);
startContainer.addChild(button);
renderer.stage.addChild(startContainer);
}
createWalls() {
console.log("created Wall");
const container = new Container();
this.renderer.stage.addChild(container);
//eftWall
const leftWall = new Graphics();
leftWall.beginFill(0xde3249);
leftWall.drawRect(0, 0, 10, this.height as number);
leftWall.position.set(0, 0);
leftWall.endFill();
container.addChild(leftWall);
//rightWall
const rightWall = new Graphics();
rightWall.beginFill(0xde3249);
rightWall.drawRect(0, 0, 10, this.height as number);
rightWall.endFill();
rightWall.position.set((this.width as number) - 10, 0);
container.addChild(rightWall);
//topWall
const topWall = new Graphics();
topWall.beginFill(0xde3249);
topWall.drawRect(0, 0, this.width as number, 10 as number);
topWall.position.set(0, 0);
topWall.endFill();
container.addChild(topWall);
//bottomWall
const bottomWall = new Graphics();
bottomWall.beginFill(0xde3249);
bottomWall.drawRect(0, 0, this.width as number, 10);
bottomWall.position.set(0, (this.height as number) - 10);
bottomWall.endFill();
this.wallArr = {
wall: container,
leftWall: leftWall,
rightWall: rightWall,
topWall: topWall,
bottomWall: bottomWall,
};
container.addChild(bottomWall);
}
createApple() {
const { width, height } = this;
this.apple = new Container();
this.renderer.stage.addChild(this.apple);
const sprite = Sprite.from(Apple);
sprite.width = 25;
sprite.height = 30;
this.apple.position.set(
Math.floor(Math.random() * (width as number) * 0.8),
Math.floor(Math.random() * (height as number) * 0.8)
);
this.apple.addChild(sprite);
}
initSnake() {
if (!this.snake) {
// console.log('~~~ create snake container ~~~~')
this.snake = new Container();
this.snake.position.set(10, 10);
this.renderer.stage.addChild(this.snake);
}
if (this.snake.children.length !== 0) {
// console.log('~~~ removeSprite ~~~~');
this.snake.removeChildren();
}
if (this.snake.children.length === 0 && this.isInitSnake) {
// console.log('~~~ first create snake ~~~');
for (let i = 0; i < 3; i++) {
let snake = new Graphics();
snake.beginFill(0x000000);
snake.drawRect(0, 0, 25, 25);
snake.position.set(27 * i, 27);
snake.endFill();
this.snake.addChild(snake);
this.snakeArr.push({
x: snake.x,
y: snake.y,
width: snake.width,
height: snake.height,
});
}
this.isInitSnake = false;
} else {
// console.log('~~~ re-render snake ~~~');
for (let i = 0; i < this.snakeArr.length; i++) {
let snake = new Graphics();
snake.beginFill(0x000000);
snake.drawRect(0, 0, 25, 25);
snake.position.set(this.snakeArr[i].x, this.snakeArr[i].y);
snake.endFill();
this.snake.addChild(snake);
}
}
}
createSnake() {
this.initSnake();
this.move();
this.state = this.play;
// 每秒帧数
this.renderer.ticker.add((delta: any) => {
this.gameLoop(delta);
});
}
addSnake({ x, y, width, height }: snakeAxisProps) {
this.snakeArr.unshift({
x: x,
y: y,
width: width,
height: height,
});
}
gameLoop(delta: number) {
this.play(delta);
}
play(_delta?: any) {
const { snakeArr, apple, wallArr, hitTestRectangle } = this;
const { leftWall, rightWall, topWall, bottomWall } = wallArr;
const snakeHead = snakeArr[snakeArr.length - 1];
const snakeLast = snakeArr[0];
if (hitTestRectangle(snakeHead, apple)) {
this.apple.removeChildren();
this.createApple();
this.setScore();
this.addSnake({ ...snakeLast, x: snakeLast.x - 27 });
}
if (hitTestRectangle(snakeHead, leftWall)) {
this.over();
}
if (hitTestRectangle(snakeHead, rightWall)) {
this.over();
}
if (hitTestRectangle(snakeHead, topWall)) {
this.over();
}
if (hitTestRectangle(snakeHead, bottomWall)) {
this.over();
}
}
snakeMove(dir?: any) {
const {
upIsDown,
leftIsDown,
downIsDown,
rightIsDown,
} = this.direction;
if (!(this.snake.children.length === 0 && this.isInitSnake) && (!upIsDown || !leftIsDown || !downIsDown || !rightIsDown)) {
const lastChildSprite = this.snakeArr[this.snakeArr.length - 1];
if (!rightIsDown) {
console.log('left');
this.speed = { ...lastChildSprite, x: lastChildSprite.x - 27 };
}
if (!downIsDown) {
console.log('up');
this.speed = { ...lastChildSprite, y: lastChildSprite.y - 27 };
}
if (!leftIsDown) {
console.log("right");
this.speed = { ...lastChildSprite, x: lastChildSprite.x + 27 };
}
if (!upIsDown) {
console.log('down');
this.speed = { ...lastChildSprite, y: lastChildSprite.y + 27 };
}
const { x, y, width, height } = this.speed;
this.snakeArr.shift();
this.snakeArr.push({
x: x,
y: y,
width: width,
height: height,
centerX: x + width / 2,
centerY: y + width / 2,
halfWidth: width / 2,
halfHeight: height / 2,
});
};
this.initSnake();
}
move() {
let left = keyboard("ArrowLeft"),
up = keyboard("ArrowUp"),
right = keyboard("ArrowRight"),
down = keyboard("ArrowDown");
right.release = () => {
this.direction = { upIsDown: true, leftIsDown: left.isDown, rightIsDown: true, downIsDown: true };
};
down.release = () => {
this.direction = { upIsDown: up.isDown, leftIsDown: true, rightIsDown: true, downIsDown: true }
};
left.release = () => {
this.direction = { upIsDown: true, leftIsDown: true, rightIsDown: right.isDown, downIsDown: true };
};
up.release = () => {
this.direction = { upIsDown: true, leftIsDown: true, rightIsDown: true, downIsDown: down.isDown };
};
}
hitTestRectangle(r1: any, r2: any) {
let hit, combinedHalfWidths, combinedHalfHeights, vx, vy;
hit = false;
r1.centerX = r1.x + r1.width / 2;
r1.centerY = r1.y + r1.height / 2;
r2.centerX = r2.x + r2.width / 2;
r2.centerY = r2.y + r2.height / 2;
r1.halfWidth = r1.width / 2;
r1.halfHeight = r1.height / 2;
r2.halfWidth = r2.width / 2;
r2.halfHeight = r2.height / 2;
vx = r1.centerX - r2.centerX;
vy = r1.centerY - r2.centerY;
combinedHalfWidths = r1.halfWidth + r2.halfWidth;
combinedHalfHeights = r1.halfHeight + r2.halfHeight;
if (Math.abs(vx) < combinedHalfWidths) {
if (Math.abs(vy) < combinedHalfHeights) {
hit = true;
} else {
hit = false;
}
} else {
hit = false;
}
return hit;
}
}
const Tanchishe = new Core({
element: "container",
width: "800",
height: "600",
});
export default Tanchishe;
打包成docker 部署
跟目录下新增一个文件夹 deploy
创建,nginx.conf
。新增一个文件dockerfile
输入
server {
listen 80;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ /index.html;
}
location /api.test/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
}
}
dockfile
输入
FROM nginx:latest
COPY ../dist/ /usr/share/nginx/html/
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
运行docker build -t tanchishe .
demo:github:tanchishe