当前位置:   article > 正文

Typescript小练习之贪吃蛇_ts练习小游戏

ts练习小游戏

前言

贪吃蛇小练习

1 搭建项目结构

设置配置文件

下载依赖

 配置less

下载依赖

修改配置文件(webpack.config.js)

引入less文件

下载兼容css版本的依赖

修改配置文件(webpack.config.js)

2 编写代码

编写代码框架(html)

编写样式(less)

编写代码逻辑(ts)

 编写食物(Food)模块

编写记分牌(ScorePanel)模块

编写蛇(Snake)模块

编写键盘控制(GameControl)模块

总结


前言

 用typescript做一个贪吃蛇的小游戏

贪吃蛇小练习

1 搭建项目结构

设置配置文件

将前面配置好的tsconfig.json,webpack.config.js,package.json文件(文末)复制粘贴到新文件中并根据项目做简单修改

 

下载依赖

npm -i

 配置less

下载依赖

npm i -D less less-loader css-loader style-loader

修改配置文件(webpack.config.js)

引入less文件

下载兼容css版本的依赖

npm i -D postcss postcss-loader postcss-preset-env

修改配置文件(webpack.config.js)

 至此,项目结构搭建完成

2 编写代码

输入npm start开启监听便于实时更新代码

编写代码框架(html)

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>贪吃蛇</title>
  8. </head>
  9. <body>
  10. <!-- 创建游戏的主容器 -->
  11. <div id="main">
  12. <!-- 设置游戏的舞台 -->
  13. <div id="stage">
  14. <!-- 设置蛇 -->
  15. <div id="snake">
  16. <!-- snake内部的div,表示蛇的各部分 -->
  17. <div>
  18. </div>
  19. </div>
  20. <!--设置食物-->
  21. <div id="food">
  22. <!-- 添加4个小div,设置食物的样式 -->
  23. <div></div>
  24. <div></div>
  25. <div></div>
  26. <div></div>
  27. </div>
  28. </div>
  29. <!-- 设置游戏的积分牌 -->
  30. <div id="score-panel">
  31. <div>
  32. SCORE:<span id="score">0</span>
  33. </div>
  34. <div>
  35. level:<span id="level">1</span>
  36. </div>
  37. </div>
  38. </div>
  39. <!-- <script src="../dist/bundle.js"></script> -->
  40. </body>
  41. </html>

index.ts文件引入less样式

编写样式(less)

  1. //设置变量
  2. @bg-color:yellow;
  3. //清除默认样式
  4. *{
  5. margin:0;
  6. padding: 0;
  7. //改变盒子模型的计算方式
  8. box-sizing: border-box;
  9. }
  10. body{
  11. font:bold 20px "Courier";
  12. }
  13. //设置主窗口的样式
  14. #main{
  15. width: 360px;
  16. height: 420px;
  17. //设置背景颜色
  18. background-color:@bg-color;
  19. margin: 100px auto;
  20. border: 10px solid blue;
  21. //设置圆角
  22. border-radius: 20px;
  23. //开启弹性盒模型
  24. display: flex;
  25. //设置主轴的方向
  26. flex-flow: column;
  27. //设置辅轴的对齐方式
  28. align-items: center;
  29. //设置主轴的对齐方式
  30. justify-content: space-around;
  31. //游戏舞台
  32. #stage{
  33. width: 304px;
  34. height: 304px;
  35. border: 2px solid black;
  36. //开启相对定位
  37. position: relative;
  38. //设置蛇的样式
  39. #snake{
  40. &>div{
  41. width: 10px;
  42. height: 10px;
  43. background-color: red;
  44. border: 1px solid @bg-color;
  45. //开启绝对定位 使蛇可以移动
  46. position: absolute;
  47. }
  48. }
  49. //设置食物
  50. #food{
  51. width: 10px;
  52. height: 10px;
  53. position: absolute;
  54. left:40px;
  55. top:100px;
  56. //background-color: red;
  57. display: flex;
  58. //设置横轴为主轴,wrap表示自动换行
  59. flex-flow: row wrap;
  60. //设置主轴和侧轴的空白空间分配到元素之间
  61. justify-content: space-between;
  62. align-content: space-between;
  63. &>div{
  64. width: 5px;
  65. height: 5px;
  66. background-color: green;
  67. border: 1px solid yellow;
  68. //使4gediv旋转45度
  69. transform: rotate(45deg);
  70. }
  71. }
  72. }
  73. //记分牌
  74. #score-panel{
  75. width: 300px;
  76. display: flex;
  77. //设置主轴的对齐方式
  78. justify-content: space-between;
  79. }
  80. }

编写代码逻辑(ts)

按不同功能分成多个模块,便于代码的编写及修改。

 将Food.ts,ScorePanel.ts,Snake.ts模块导入到GameControl.ts模块,由该模块来统一整合。

index.ts文件引入各模块

 

 编写食物(Food)模块

  1. //定义类
  2. class Food{
  3. //定义一个属性表示食物所对应的元素
  4. element:HTMLElement;
  5. constructor(){
  6. //获取页面中的food的元素并将其赋值给element
  7. this.element=document.getElementById("food")!;//!表示元素不可能为空
  8. }
  9. //定义一个获取食物X轴坐标的方法
  10. get X(){
  11. return this.element.offsetLeft;
  12. }
  13. //定义一个获取食物Y轴坐标的方法
  14. get Y(){
  15. return this.element.offsetTop;
  16. }
  17. //修改食物的位置
  18. change(){
  19. //生成一个随机的位置
  20. //食物的位置最小是0,最大是290 300-10=290
  21. //蛇移动一次就是一格,一格的大小就是10,所以就要求食物的坐标必须是10的倍数
  22. //Math.round表示四舍五入
  23. //Math.floor(Math.random()*30)*10;
  24. //Math.floor表示向下取整
  25. let top=Math.round(Math.random()*29)*10;//既包括0,又包括290,而且都是10的倍数
  26. let left=Math.round(Math.random()*29)*10;
  27. this.element.style.left=left+'px';
  28. this.element.style.top=top+'px';
  29. }
  30. }
  31. //测试代码
  32. // const food=new Food()
  33. // console.log(food.X,food.Y);
  34. // food.change();
  35. // console.log(food.X,food.Y);
  36. export default Food;

编写记分牌(ScorePanel)模块

  1. //定义表示记分牌的类
  2. class ScorePanel{
  3. //score和level用来记录分数和等级
  4. score=0;
  5. level=1;
  6. //分数和等级所在的元素,在构造函数中进行初始化
  7. scoreEle:HTMLElement;
  8. levelEle:HTMLElement;
  9. //设置变量限制等级
  10. maxLevel:number;
  11. //设置一个变量表示多少分时升级
  12. upScore:number;
  13. constructor(maxLevel:number=10,upScore:number=10){//不传默认10,穿了多少就多少
  14. this.scoreEle=document.getElementById("score")!;
  15. this.levelEle=document.getElementById("level")!;
  16. this.maxLevel=maxLevel;
  17. this.upScore=upScore;
  18. }
  19. //设置一个加分的方法
  20. addScore(){
  21. //使分数自增
  22. //this.score++;
  23. this.scoreEle.innerHTML=++this.score+'';//装换成字符串,innerHTML接收字符串
  24. //判断分数是多少
  25. if(this.score%this.upScore===0){
  26. this.levelUp();
  27. }
  28. }
  29. //提升等级的方法
  30. levelUp(){
  31. if(this.level<=this.maxLevel){
  32. this.levelEle.innerHTML=++this.level+'';
  33. }
  34. }
  35. }
  36. //测试代码
  37. //const scorePanel=new ScorePanel(1,0);
  38. // scorePanel.addScore();
  39. // scorePanel.addScore();
  40. // scorePanel.addScore();
  41. // for(let i=0;i<9;i++)
  42. // {
  43. // scorePanel.addScore();
  44. // }
  45. export default ScorePanel;//改为默认模块暴露出去

编写蛇(Snake)模块

  1. class Snake{
  2. //表示蛇头的元素
  3. head:HTMLElement;
  4. //蛇的身体(包括蛇头)
  5. bodies:HTMLCollection;
  6. //获取蛇的容器
  7. element:HTMLElement;
  8. constructor(){
  9. this.element=document.getElementById("snake")!;
  10. this.head=document.querySelector("#snake>div")!as HTMLElement;
  11. this.bodies=this.element.getElementsByTagName("div");
  12. }
  13. //获取蛇的坐标(蛇头坐标)
  14. get X(){
  15. return this.head.offsetLeft;
  16. }
  17. //获取蛇的Y轴坐标
  18. get Y(){
  19. return this.head.offsetTop;
  20. }
  21. //设置蛇头的坐标
  22. set X(value:number){
  23. //如果新值和旧值相同,则直接返回不在修改
  24. if(this.X===value){
  25. return;
  26. }
  27. //x的值的合法范围0-290
  28. if(value<0||value>290){
  29. //进入判断说明蛇撞墙了
  30. throw new Error("蛇撞墙了");
  31. }
  32. //修改x时,是在修改水平坐标,蛇在左右移动,蛇在向左移动式时,不能向右掉头,反之亦然
  33. if(this.bodies[1]&&(this.bodies[1]as HTMLElement).offsetLeft===value){
  34. //console.log("发生了水平掉头");
  35. //如果发生了掉头,则应让蛇向反方向移动
  36. if(value>this.X){
  37. //如果新值大于旧值,则说明蛇在向右走(实际是蛇向左走),此时发生掉头.但实际上应该使蛇继续向左走
  38. value=this.X-10;
  39. }else{
  40. value=this.X+10;
  41. }
  42. }
  43. //移动身体
  44. this.moveBody();
  45. this.head.style.left=value+"px";
  46. this.checkHeadBody();
  47. }
  48. set Y(value:number){
  49. if(this.Y===value){
  50. return;
  51. }
  52. //y的值的合法范围0-290
  53. if(value<0||value>290){
  54. //进入判断说明蛇撞墙了
  55. throw new Error("蛇撞墙了");
  56. }
  57. //修改y时,是在修改垂直坐标,蛇在上下移动,蛇在向上移动式时,不能向下掉头,反之亦然
  58. if(this.bodies[1]&&(this.bodies[1]as HTMLElement).offsetTop===value){
  59. //console.log("发生了垂直掉头");
  60. //如果发生了掉头,则应让蛇向反方向移动
  61. if(value>this.Y){
  62. //如果新值大于旧值,则说明蛇在向下走(实际是蛇向上走),此时发生掉头.但实际上应该使蛇继续向上走
  63. value=this.Y-10;
  64. }else{
  65. value=this.Y+10;
  66. }
  67. }
  68. //移动身体
  69. this.moveBody();
  70. this.head.style.top=value+"px";
  71. //检查有没有撞到自己
  72. this.checkHeadBody();
  73. }
  74. //蛇增加身体的方法
  75. addBody(){
  76. //向element中添加一个div
  77. this.element.insertAdjacentHTML("beforeend","<div></div>");
  78. }
  79. //添加一个蛇身体移动的方法
  80. moveBody(){
  81. //将后边的身体设置为前边身体的位置
  82. // 第4节=第3节的位置
  83. // 第3节=第2节的位置
  84. // 第2节=第1节的位置
  85. //遍历获取所有的身体
  86. for(let i=this.bodies.length-1;i>0;i--)
  87. {
  88. //获取前边身体的位置
  89. let X=(this.bodies[i-1]as HTMLElement).offsetLeft;//没加类型断言报错是因为bodies类型是Element,另外一个是htmlelement
  90. let Y=(this.bodies[i-1]as HTMLElement).offsetTop;
  91. //将值设置到当前身体上
  92. (this.bodies[i]as HTMLElement).style.left=X+"px";
  93. (this.bodies[i]as HTMLElement).style.top=Y+"px";
  94. }
  95. }
  96. //检查蛇头是否撞到自己的方法
  97. checkHeadBody(){
  98. //获取所有的身体,检查其是否和蛇头的坐标发生重叠
  99. for(let i=1;i<this.bodies.length;i++){
  100. let bd=this.bodies[i]as HTMLElement;
  101. if(this.X===bd.offsetLeft && this.Y===bd.offsetTop){
  102. //进入判断说明蛇头撞到了身体,游戏结束
  103. throw new Error("撞到自己了");
  104. }
  105. }
  106. }
  107. }
  108. export default Snake;

编写键盘控制(GameControl)模块

  1. //引入其他类
  2. import Snake from "./snake";
  3. import Food from "./Food";
  4. import ScorePanel from "./ScorePanel";
  5. //游戏的控制器,控制其他所有的类
  6. class GameControl{
  7. //定义三个属性
  8. //蛇
  9. snake:Snake;
  10. //食物
  11. food:Food;
  12. //记分牌
  13. scorepanel:ScorePanel;
  14. //创建一个属性来存储蛇的移动方向(也就是按键的方向)
  15. direction:string='';
  16. //创建一个属性用来记录游戏是否结束
  17. isLive=true;
  18. constructor(){
  19. this.snake=new Snake();
  20. this.food=new Food();
  21. this.scorepanel=new ScorePanel();
  22. this.init();
  23. }
  24. //游戏的初始化方法,调用后游戏即开始
  25. init(){
  26. //绑定键盘按下的事件
  27. document.addEventListener('keydown',this.keydownHandle.bind(this));//加了bind使得括号里的this指向keydownHandle
  28. //调用run方法。使蛇移动,没开定时器时,蛇只动一次,因为只调用一次
  29. this.run();
  30. }
  31. /*
  32. ArrowUp Up
  33. ArrowDown Down
  34. ArrowLeft Left
  35. ArrowRight Right
  36. */
  37. //创建一个键盘按下的响应函数
  38. keydownHandle(event:KeyboardEvent){
  39. //console.log(this);没加bind之前 this指向是document
  40. //需要检查event.key的值是否合法(用户是否按了正确的按键)
  41. //修改direction属性
  42. this.direction=event.key;
  43. }
  44. //创建一个控制蛇移动的方法
  45. run(){
  46. //根据方向(this.direction)来使蛇的位置改变
  47. /*
  48. 向上:top减少
  49. 向下:top增加
  50. 向左: left减少
  51. 向右;left增加
  52. */
  53. //获取蛇现在的坐标
  54. let X=this.snake.X;
  55. let Y=this.snake.Y;
  56. //根据按键方向来修改x值和y值
  57. switch(this.direction){
  58. case "ArrowUp":
  59. case "Up":
  60. Y-=10;
  61. break;
  62. case "ArrowDown":
  63. case "Down":
  64. Y+=10;
  65. break;
  66. case "ArrowLeft":
  67. case "Left":
  68. X-=10;
  69. break;
  70. case "ArrowRight":
  71. case "Right":
  72. X+=10;
  73. break;
  74. }
  75. //检查蛇是否吃到了食物
  76. this.checkEat(X,Y);
  77. //修改蛇的x值和y值
  78. try{
  79. this.snake.X=X;
  80. this.snake.Y=Y;
  81. }catch(e:any){
  82. //进入到catch,说明出现了异常,游戏结束,弹出一个提示信息
  83. alert(e.message+"GAME OVER");
  84. //将isLive设置为false
  85. this.isLive=false;
  86. }
  87. //开启一个定时调用
  88. this.isLive&&setTimeout(this.run.bind(this),300-(this.scorepanel.level-1)*30);
  89. }
  90. //定义一个方法,用来检查蛇是否吃到食物
  91. checkEat(X:number,Y:number){
  92. if( X===this.food.X&&Y===this.food.Y){
  93. //console.log("吃到食物了");
  94. //食物的位置涛进行重置
  95. this.food.change();
  96. //分数增加
  97. this.scorepanel.addScore();
  98. //蛇要增加一节
  99. this.snake.addBody();
  100. }
  101. }
  102. }
  103. export default GameControl;

至此,项目完成,输入npm run build即可运行代码

注:所需的配置文件如下

tsconfig.json

  1. {
  2. "compilerOptions": {
  3. "module": "es2015",
  4. "target": "es2015",
  5. "strict": true,
  6. "noEmitOnError": true
  7. }
  8. }

package.json

  1. {
  2. "name": "part2",
  3. "version": "1.0.0",
  4. "description": "",
  5. "main": "index.js",
  6. "scripts": {
  7. "test": "echo \"Error: no test specified\" && exit 1",
  8. "build": "webpack --mode development",
  9. "start": "webpack serve --open --mode development "
  10. },
  11. "keywords": [],
  12. "author": "",
  13. "license": "ISC",
  14. "devDependencies": {
  15. "@babel/core": "^7.19.3",
  16. "@babel/preset-env": "^7.19.4",
  17. "babel-loader": "^8.2.5",
  18. "clean-webpack-plugin": "^4.0.0",
  19. "core-js": "^3.25.5",
  20. "css-loader": "^6.7.1",
  21. "html-webpack-plugin": "^5.5.0",
  22. "less": "^4.1.3",
  23. "less-loader": "^11.1.0",
  24. "postcss": "^8.4.18",
  25. "postcss-loader": "^7.0.1",
  26. "postcss-preset-env": "^7.8.2",
  27. "style-loader": "^3.3.1",
  28. "ts-loader": "^9.4.1",
  29. "typescript": "^4.8.4",
  30. "webpack": "^5.74.0",
  31. "webpack-cli": "^4.10.0",
  32. "webpack-dev-server": "^4.11.1"
  33. }
  34. }

webpack.config.js

  1. //引入一个包
  2. const path=require("path");
  3. //引入html插件
  4. const HTMLWebpackPlugin=require('html-webpack-plugin');
  5. //引入clean插件
  6. const {CleanWebpackPlugin}=require('clean-webpack-plugin');
  7. const { resolve } = require("path");
  8. //webpack中的所有的配置信息都应该写在module.exports中
  9. module.exports={
  10. //指定入口文件
  11. entry:"./src/index.ts",
  12. //指定打包文件所在的目录
  13. output:{
  14. //指定打包文件的目录
  15. path:path.resolve(__dirname,"dist"),
  16. //打包后文件的文件
  17. filename:"bundle.js",
  18. //告诉webpack不使用箭头函数
  19. environment:{
  20. arrowFunction:false,
  21. const:false
  22. }
  23. },
  24. //指定webpack打包时要使用的模块
  25. module:{
  26. //指定要加载的规则
  27. rules:[
  28. {
  29. //test指定规则生效的文件
  30. test:/\.ts$/,
  31. //要使用的loader
  32. use:[
  33. //配置babel
  34. {
  35. //指定加载器
  36. loader:'babel-loader',
  37. //设置babel
  38. options:{
  39. //设置预定义的环境
  40. presets:[
  41. [
  42. //指定环境的插件
  43. "@babel/preset-env",
  44. //配置信息
  45. {
  46. //要兼容的目标浏览器
  47. targets:{
  48. "edge":"106",
  49. "ie":"11",
  50. "chrome":"99"
  51. },
  52. //指定corejs的版本
  53. "corejs":"3",
  54. //使用corejs的方式 "usage"表示按需加载
  55. "useBuiltIns":"usage"
  56. }
  57. ]
  58. ]
  59. }
  60. },
  61. 'ts-loader',
  62. ],
  63. //要排除的文件
  64. exclude:/node_modules/
  65. },
  66. //设置less文件的处理
  67. {
  68. test:/\.less$/,
  69. use:[
  70. "style-loader",
  71. "css-loader",
  72. //引入postcss
  73. {
  74. loader:"postcss-loader",
  75. options:{
  76. postcssOptions:{
  77. plugins:[
  78. [
  79. 'postcss-preset-env',
  80. {
  81. browsers:"last 2 versions"//兼容两种最新的浏览器
  82. }
  83. ]
  84. ]
  85. }
  86. }
  87. },
  88. "less-loader"//loader执行顺序从下往上
  89. ]
  90. }
  91. ]
  92. },
  93. //配置webpack插件
  94. plugins:[
  95. new CleanWebpackPlugin(),
  96. new HTMLWebpackPlugin({
  97. //title:"这是一个自定义的title"
  98. template:"./src/index.html"//以他作为模板生成js文件
  99. }),
  100. ],
  101. //用来设置引用的模块
  102. resolve:{
  103. extensions:['.ts','.js']//以.ts ,.js结尾的文件都可以作为模块来使用
  104. }
  105. }

总结

做了小练习来熟悉typescript,对typescript语法及使用有了进一步的了解,提高了编程能力。

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号