1、写一个简单的贪吃蛇

需求分析

使用pixijs写一个简单的贪吃蛇
Pixi(贪吃蛇) - 图1

搭建环境

使用@webpack-cli/generators搭建一个ts环境
@webpack-cli/generators

  1. //add npm
  2. npm init
  3. //add webpack
  4. yarn add -D @webpack-cli/generators webpack webpack-cli
  5. //webpack init 新版本可以不推荐使用webpack-cli,根据返回提示来操作就行了
  6. yarn webpack init
  7. // 启动项目
  8. yarn serve

根据选项,选择ts
项目结构
image.png
在src中创建一个tanchishe.ts的文件,在index.ts中调用

  1. //index.ts
  2. import Tanchishe from './tanchishe';
  3. Tanchishe;
  1. //tanchishe.ts
  2. import PIXI from 'pixi.js';
  3. class Core {
  4. }
  5. const Tanchishe = new Core();
  6. export default Tanchishe;

根据思维导图的逻辑来

  1. i
  2. mport {
  3. Application,
  4. Container,
  5. Sprite,
  6. Loader,
  7. Graphics,
  8. Text,
  9. TextStyle,
  10. ParticleContainer,
  11. Texture,
  12. TilingSprite,
  13. } from "pixi.js";
  14. import { keyboard } from "./keyboard";
  15. import Apple from "./assets/apple.jpg";
  16. interface CoreProps {
  17. element: string;
  18. width: string | number;
  19. height: string | number;
  20. isLoader?: boolean;
  21. }
  22. interface snakeAxisProps {
  23. x?: number;
  24. y?: number;
  25. width?: number;
  26. height?: number;
  27. }
  28. interface directionProps {
  29. upIsDown: boolean;
  30. leftIsDown: boolean;
  31. downIsDown: boolean;
  32. rightIsDown: boolean;
  33. }
  34. class Core {
  35. element: Element;
  36. width: string | number;
  37. height: string | number;
  38. renderer!: any;
  39. loader!: any;
  40. isLoader?: boolean;
  41. isVisible: boolean;
  42. speed: snakeAxisProps;
  43. snake?: any;
  44. snakeArr?: any[];
  45. isInitSnake: boolean;
  46. apple?: any;
  47. wallArr?: any;
  48. state: any;
  49. score: number;
  50. direction: directionProps;
  51. timer:null | NodeJS.Timer
  52. constructor(option: CoreProps) {
  53. const { element, width, height, isLoader = false } = option;
  54. this.element = document.querySelector(`#${element}`);
  55. this.width = width;
  56. this.height = height;
  57. this.isLoader = isLoader;
  58. this.snake = null;
  59. this.snakeArr = [];
  60. this.apple = null;
  61. this.score = 0;
  62. this.isVisible = false;
  63. this.renderer = null;
  64. this.isInitSnake = true;
  65. this.direction = {
  66. upIsDown: true,
  67. leftIsDown: true,
  68. downIsDown: true,
  69. rightIsDown: true,
  70. };
  71. this.timer = null;
  72. this.speed = {};
  73. this.init();
  74. }
  75. init() {
  76. console.log("init");
  77. this.apple = null;
  78. this.wallArr = null;
  79. this.renderer = new Application({
  80. width: this.width as number,
  81. height: this.height as number,
  82. backgroundColor: 0xffffff,
  83. });
  84. this.getScore();
  85. this.createRender();
  86. }
  87. loaderImg(option: {
  88. imgUrl: string;
  89. loaded: (loader: any, resources: any) => void;
  90. }) {
  91. const { imgUrl, loaded } = option;
  92. this.loader = Loader.shared;
  93. this.loader.add("apple", imgUrl).load(loaded);
  94. }
  95. createRender() {
  96. this.renderer.stage.removeChildren();
  97. this.element?.appendChild(this.renderer.view);
  98. console.log(this.isVisible);
  99. if (this.isVisible) {
  100. this.createWalls();
  101. this.createSnake();
  102. this.createApple();
  103. //移动蛇
  104. this.timer = setInterval(()=>{
  105. this.snakeMove();
  106. },100)
  107. } else {
  108. this.startPage();
  109. }
  110. }
  111. getScore() {
  112. const body = document.body;
  113. const scoreWrapper = document.createElement("div");
  114. scoreWrapper.innerHTML = `
  115. <span>分数:</span><span id="Score">0</span>
  116. `;
  117. body.appendChild(scoreWrapper);
  118. }
  119. setScore() {
  120. this.score++;
  121. const Score = document.querySelector("#Score");
  122. Score.innerHTML = `${this.score}`;
  123. }
  124. over() {
  125. this.renderer.stage.removeChildren();
  126. clearInterval(this.timer);
  127. this.endPage();
  128. }
  129. endPage() {
  130. const { width, height, renderer, score } = this;
  131. const graphics = new Graphics();
  132. graphics.beginFill(0x000000);
  133. graphics.drawRect(0, 0, width as number, height as number);
  134. graphics.endFill();
  135. renderer.stage.addChild(graphics);
  136. const endText = new Text(
  137. `游戏结束,你的分数为:${score}`,
  138. new TextStyle({
  139. fill: 0xcccccc,
  140. })
  141. );
  142. renderer.stage.addChild(endText);
  143. }
  144. startPage() {
  145. const { width, height, renderer } = this;
  146. const startContainer = new Container();
  147. const graphics = new Graphics();
  148. graphics.beginFill(0x000000);
  149. graphics.drawRect(0, 0, width as number, height as number);
  150. graphics.endFill();
  151. startContainer.addChild(graphics);
  152. const style = new TextStyle({
  153. fontFamily: "Arial",
  154. fontSize: 36,
  155. fill: "white",
  156. stroke: "#ff3300",
  157. strokeThickness: 4,
  158. dropShadow: true,
  159. dropShadowColor: "#cccccc",
  160. dropShadowBlur: 4,
  161. dropShadowAngle: Math.PI / 6,
  162. dropShadowDistance: 6,
  163. });
  164. const message = new Text("贪吃蛇(乞丐版)", style);
  165. message.position.set((width as number) / 3, (height as number) / 4);
  166. startContainer.addChild(message);
  167. const border = new Graphics();
  168. border.beginFill(0xcccccc);
  169. border.lineStyle({ color: 0xffffff, width: 4, alignment: 0 });
  170. border.drawRect(0, 0, 120, 60);
  171. border.endFill();
  172. border.position.set((this.width as number) / 2.5, (height as number) / 2);
  173. startContainer.addChild(border);
  174. const button = new Text("游戏开始");
  175. border.interactive = true;
  176. border.on("pointerdown", (e) => {
  177. this.isVisible = true;
  178. this.createRender();
  179. });
  180. button.position.set((width as number) / 2.45, (height as number) / 1.9);
  181. startContainer.addChild(button);
  182. renderer.stage.addChild(startContainer);
  183. }
  184. createWalls() {
  185. console.log("created Wall");
  186. const container = new Container();
  187. this.renderer.stage.addChild(container);
  188. //eftWall
  189. const leftWall = new Graphics();
  190. leftWall.beginFill(0xde3249);
  191. leftWall.drawRect(0, 0, 10, this.height as number);
  192. leftWall.position.set(0, 0);
  193. leftWall.endFill();
  194. container.addChild(leftWall);
  195. //rightWall
  196. const rightWall = new Graphics();
  197. rightWall.beginFill(0xde3249);
  198. rightWall.drawRect(0, 0, 10, this.height as number);
  199. rightWall.endFill();
  200. rightWall.position.set((this.width as number) - 10, 0);
  201. container.addChild(rightWall);
  202. //topWall
  203. const topWall = new Graphics();
  204. topWall.beginFill(0xde3249);
  205. topWall.drawRect(0, 0, this.width as number, 10 as number);
  206. topWall.position.set(0, 0);
  207. topWall.endFill();
  208. container.addChild(topWall);
  209. //bottomWall
  210. const bottomWall = new Graphics();
  211. bottomWall.beginFill(0xde3249);
  212. bottomWall.drawRect(0, 0, this.width as number, 10);
  213. bottomWall.position.set(0, (this.height as number) - 10);
  214. bottomWall.endFill();
  215. this.wallArr = {
  216. wall: container,
  217. leftWall: leftWall,
  218. rightWall: rightWall,
  219. topWall: topWall,
  220. bottomWall: bottomWall,
  221. };
  222. container.addChild(bottomWall);
  223. }
  224. createApple() {
  225. const { width, height } = this;
  226. this.apple = new Container();
  227. this.renderer.stage.addChild(this.apple);
  228. const sprite = Sprite.from(Apple);
  229. sprite.width = 25;
  230. sprite.height = 30;
  231. this.apple.position.set(
  232. Math.floor(Math.random() * (width as number) * 0.8),
  233. Math.floor(Math.random() * (height as number) * 0.8)
  234. );
  235. this.apple.addChild(sprite);
  236. }
  237. initSnake() {
  238. if (!this.snake) {
  239. // console.log('~~~ create snake container ~~~~')
  240. this.snake = new Container();
  241. this.snake.position.set(10, 10);
  242. this.renderer.stage.addChild(this.snake);
  243. }
  244. if (this.snake.children.length !== 0) {
  245. // console.log('~~~ removeSprite ~~~~');
  246. this.snake.removeChildren();
  247. }
  248. if (this.snake.children.length === 0 && this.isInitSnake) {
  249. // console.log('~~~ first create snake ~~~');
  250. for (let i = 0; i < 3; i++) {
  251. let snake = new Graphics();
  252. snake.beginFill(0x000000);
  253. snake.drawRect(0, 0, 25, 25);
  254. snake.position.set(27 * i, 27);
  255. snake.endFill();
  256. this.snake.addChild(snake);
  257. this.snakeArr.push({
  258. x: snake.x,
  259. y: snake.y,
  260. width: snake.width,
  261. height: snake.height,
  262. });
  263. }
  264. this.isInitSnake = false;
  265. } else {
  266. // console.log('~~~ re-render snake ~~~');
  267. for (let i = 0; i < this.snakeArr.length; i++) {
  268. let snake = new Graphics();
  269. snake.beginFill(0x000000);
  270. snake.drawRect(0, 0, 25, 25);
  271. snake.position.set(this.snakeArr[i].x, this.snakeArr[i].y);
  272. snake.endFill();
  273. this.snake.addChild(snake);
  274. }
  275. }
  276. }
  277. createSnake() {
  278. this.initSnake();
  279. this.move();
  280. this.state = this.play;
  281. // 每秒帧数
  282. this.renderer.ticker.add((delta: any) => {
  283. this.gameLoop(delta);
  284. });
  285. }
  286. addSnake({ x, y, width, height }: snakeAxisProps) {
  287. this.snakeArr.unshift({
  288. x: x,
  289. y: y,
  290. width: width,
  291. height: height,
  292. });
  293. }
  294. gameLoop(delta: number) {
  295. this.play(delta);
  296. }
  297. play(_delta?: any) {
  298. const { snakeArr, apple, wallArr, hitTestRectangle } = this;
  299. const { leftWall, rightWall, topWall, bottomWall } = wallArr;
  300. const snakeHead = snakeArr[snakeArr.length - 1];
  301. const snakeLast = snakeArr[0];
  302. if (hitTestRectangle(snakeHead, apple)) {
  303. this.apple.removeChildren();
  304. this.createApple();
  305. this.setScore();
  306. this.addSnake({ ...snakeLast, x: snakeLast.x - 27 });
  307. }
  308. if (hitTestRectangle(snakeHead, leftWall)) {
  309. this.over();
  310. }
  311. if (hitTestRectangle(snakeHead, rightWall)) {
  312. this.over();
  313. }
  314. if (hitTestRectangle(snakeHead, topWall)) {
  315. this.over();
  316. }
  317. if (hitTestRectangle(snakeHead, bottomWall)) {
  318. this.over();
  319. }
  320. }
  321. snakeMove(dir?: any) {
  322. const {
  323. upIsDown,
  324. leftIsDown,
  325. downIsDown,
  326. rightIsDown,
  327. } = this.direction;
  328. if (!(this.snake.children.length === 0 && this.isInitSnake) && (!upIsDown || !leftIsDown || !downIsDown || !rightIsDown)) {
  329. const lastChildSprite = this.snakeArr[this.snakeArr.length - 1];
  330. if (!rightIsDown) {
  331. console.log('left');
  332. this.speed = { ...lastChildSprite, x: lastChildSprite.x - 27 };
  333. }
  334. if (!downIsDown) {
  335. console.log('up');
  336. this.speed = { ...lastChildSprite, y: lastChildSprite.y - 27 };
  337. }
  338. if (!leftIsDown) {
  339. console.log("right");
  340. this.speed = { ...lastChildSprite, x: lastChildSprite.x + 27 };
  341. }
  342. if (!upIsDown) {
  343. console.log('down');
  344. this.speed = { ...lastChildSprite, y: lastChildSprite.y + 27 };
  345. }
  346. const { x, y, width, height } = this.speed;
  347. this.snakeArr.shift();
  348. this.snakeArr.push({
  349. x: x,
  350. y: y,
  351. width: width,
  352. height: height,
  353. centerX: x + width / 2,
  354. centerY: y + width / 2,
  355. halfWidth: width / 2,
  356. halfHeight: height / 2,
  357. });
  358. };
  359. this.initSnake();
  360. }
  361. move() {
  362. let left = keyboard("ArrowLeft"),
  363. up = keyboard("ArrowUp"),
  364. right = keyboard("ArrowRight"),
  365. down = keyboard("ArrowDown");
  366. right.release = () => {
  367. this.direction = { upIsDown: true, leftIsDown: left.isDown, rightIsDown: true, downIsDown: true };
  368. };
  369. down.release = () => {
  370. this.direction = { upIsDown: up.isDown, leftIsDown: true, rightIsDown: true, downIsDown: true }
  371. };
  372. left.release = () => {
  373. this.direction = { upIsDown: true, leftIsDown: true, rightIsDown: right.isDown, downIsDown: true };
  374. };
  375. up.release = () => {
  376. this.direction = { upIsDown: true, leftIsDown: true, rightIsDown: true, downIsDown: down.isDown };
  377. };
  378. }
  379. hitTestRectangle(r1: any, r2: any) {
  380. let hit, combinedHalfWidths, combinedHalfHeights, vx, vy;
  381. hit = false;
  382. r1.centerX = r1.x + r1.width / 2;
  383. r1.centerY = r1.y + r1.height / 2;
  384. r2.centerX = r2.x + r2.width / 2;
  385. r2.centerY = r2.y + r2.height / 2;
  386. r1.halfWidth = r1.width / 2;
  387. r1.halfHeight = r1.height / 2;
  388. r2.halfWidth = r2.width / 2;
  389. r2.halfHeight = r2.height / 2;
  390. vx = r1.centerX - r2.centerX;
  391. vy = r1.centerY - r2.centerY;
  392. combinedHalfWidths = r1.halfWidth + r2.halfWidth;
  393. combinedHalfHeights = r1.halfHeight + r2.halfHeight;
  394. if (Math.abs(vx) < combinedHalfWidths) {
  395. if (Math.abs(vy) < combinedHalfHeights) {
  396. hit = true;
  397. } else {
  398. hit = false;
  399. }
  400. } else {
  401. hit = false;
  402. }
  403. return hit;
  404. }
  405. }
  406. const Tanchishe = new Core({
  407. element: "container",
  408. width: "800",
  409. height: "600",
  410. });
  411. export default Tanchishe;

打包成docker 部署

跟目录下新增一个文件夹 deploy创建,nginx.conf。新增一个文件dockerfile
输入

  1. server {
  2. listen 80;
  3. root /usr/share/nginx/html;
  4. location / {
  5. try_files $uri $uri/ /index.html;
  6. }
  7. location /api.test/ {
  8. proxy_pass http://127.0.0.1:8080/;
  9. proxy_set_header Host $host;
  10. }
  11. }

dockfile 输入

  1. FROM nginx:latest
  2. COPY ../dist/ /usr/share/nginx/html/
  3. COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf

运行docker build -t tanchishe .
image.png

demo:github:tanchishe