共计 6573 个字符,预计需要花费 17 分钟才能阅读完成。
参考文章:https://www.bilibili.com/video/BV1Xy4y1v7S2?p=22
项目搭建
将之前的 package.json,tsconfig.json,webpack.config.js 复制到新项目中,同时新建 src 跟 test 目录。
然后修改 package.json 中的 name,修改成现在的项目名称。
然后使用 npm i
自动下载项目所需的包,或者直接使用图形化命令下载也行。
或
搭建完后测试一下是否正常。在 src 下新建 main.ts 与 main.html,随意在 main.ts 中写一些代码。
写完后使用 npm run build
来构建一下。正常完成后会构建一个 dict 文件夹。
配置 less
使用 npm i -D less less-loader css-loader style-loader
下载。然后修改 webpack.config.js 文件。
添加的配置信息
{
test: /.less$/,
use:[
"style-loader",
"css-loader",
"less-loader"
],
exclude: /node-modules/
}
测试
创建一个 less 文件,随意写些内容,然后再 ts 中引入。
使用 npm run build
后页面变成绿色了,这代表 less 的代码执行成功。
css 的兼容性处理
跟 ts 一样,css 也要考虑兼容性问题。使用 npm i -D postcss postcss-loader postcss-preset-env
下载相关构建。然后修改 webpack.config.js 文件。
{
loader: "postcss-loader",
options: {
postcssOptions:{
plugins:[
[
"postcss-preset-env",
{
browsers: "last 2 versions"
}
]
]
}
}
},
最终 webpack.config.js 中的 less 配置信息如下:
{
test: /.less$/,
use:[
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions:{
plugins:[
[
"postcss-preset-env",
{
browsers: "last 2 versions"
}
]
]
}
}
},
"less-loader"
],
exclude: /node-modules/
}
项目界面
DOCTYPE html>
html lang="en">
head>
meta charset="UTF-8">
title>贪食蛇title>
head>
body>
div id="main">
div id="stage">
div id="snake">
div>div>
div>
div id="food">
div>div>
div>div>
div>div>
div>div>
div>
div>
div id="score-panel">
div>
SCORE:span id="score">0span>
div>
div>
level:span id="level">1span>
div>
div>
div>
body>
html>
设置样式
body{
background-color: #1E90FF;
display: flex;
font:bold 20px "Courier New"; // 统一设置字体
}
// 设置变量
@bg-color: #b7d4a8;
// 清除默认样式
*{
margin: 0;
padding: 0;
// 改变盒子模型的计算方式, 方便自动计算内容器大小
box-sizing: border-box;
}
// 设置主窗口的样式
#main{
width: 360px;
height: 420px;
background-color: @bg-color; // 设置主容器背景颜色
margin: 100px auto; // 位置下移 100 像素,水平自动居中
border: 10px solid black; // 设置边界
border-radius: 40px; // 设置圆角
display: flex; // 开启弹性盒模型
flex-flow:column; // 设置主轴方向
align-items: center; // 设置侧轴的对其方式
justify-content: space-around; // 设置主轴的对齐方式
// 游戏舞台
#stage{
width: 304px;
height: 304px;
border: 2px solid black; // 设置边界
position: relative; // 开启相对定位
// 设置蛇的样式
#snake{
&>div{
width: 10px;
height: 10px;
background-color: black;
border: 1px solid @bg-color;
position: absolute; // 开启绝对定位
}
}
}
// 设置食物的样式
#food{
width: 10px;
height: 10px;
border: 1px solid @bg-color;
position: absolute; // 开启绝对定位
display: flex; // 开启弹性盒模型
flex-flow: row wrap; // row 设置横轴为主轴, wrap 表示会自动换行
justify-content: space-between; // 设置主轴和侧轴的空白空间分配到元素之间
align-content: space-between;
//left: 40px;
//top: 100px;
&>div{
width: 4px;
height: 4px;
background-color: black;
transform: rotate(45deg); // 设置旋转 45 度
}
}
// 游戏记分牌
#score-panel{
width: 300px;
display: flex;
justify-content: space-between; // 设置主轴对齐方式
}
}
运行逻辑
完成 FOOD 类
新建一个 Food.ts 来存放该类
class Food{
element: HTMLElement
stage
constructor() {
this.element = document.getElementById("food")!
this.stage = document.getElementById("stage")!
}
get X(){
return this.element.offsetLeft
}
get Y(){
return this.element.offsetTop
}
change(){
const high = (Math.floor(this.stage.offsetHeight/10)*10 - 10)/10
const width= (Math.floor(this.stage.offsetWidth/10)*10 - 10)/10
let x = Math.round(Math.random() * width) * 10
let y = Math.round(Math.random() * high) * 10
this.element.style.left = x + "px"
this.element.style.top = y + "px"
}
}
export default Food
完成 ScorePanel 类
新建一个 ScorePanel.ts 来存放该类
class ScorePanel{
#score:number = 0
level:number=1
#score_ele:HTMLElement
level_ele:HTMLElement
max_level:number
up_score:number
constructor(max_level:number = 10, up_score:number = 10) {
this.#score_ele = document.getElementById("score")!
this.level_ele = document.getElementById("level")!
this.max_level = max_level
this.up_score = up_score
}
addScore(){
this.#score_ele.innerHTML = ++this.#score + ''
if(this.#score % this.up_score === 0){
this.levelUp()
}
}
levelUp(){
if (this.level this.max_level){
this.level_ele.innerHTML = ++this.level + ''
}
}
}
const sp = new ScorePanel()
for (let i=0;i10;i++){
sp.addScore()
}
export default ScorePanel
完成 Snake 类
蛇的相关属性方法
class Snake{
head:HTMLElement
bodis:HTMLCollectionBase
snake:HTMLElement
stage:HTMLElement
stage_width:number
stage_high:number
is_reback_way:boolean = false
constructor() {
this.snake = document.getElementById("snake")!
this.head = document.querySelector('#snake > div') as HTMLElement
this.bodis = this.snake.getElementsByTagName("div")
this.stage = document.getElementById("stage")!
this.stage_width= (Math.floor(this.stage.offsetWidth/10)*10 - 10)
this.stage_high = (Math.floor(this.stage.offsetHeight/10)*10 - 10)
}
get X(){
return this.head.offsetLeft
}
get Y(){
return this.head.offsetTop
}
set X(value){
if(this.X === value){
return
}
if (value 0 || value > this.stage_width){
throw new Error('蛇撞墙了')
}
this.moveBody()
this.head.style.left = value + "px"
this.checkHeadBody()
}
set Y(value){
if(this.Y === value){
return
}
if (value 0 || value > this.stage_high){
throw new Error('蛇撞墙了')
}
this.moveBody()
this.head.style.top = value + "px"
this.checkHeadBody()
}
addBody(){
this.snake.insertAdjacentHTML("beforeend","")
}
moveBody(){
for (let i=this.bodis.length-1; i > 0; i--){
let x = (this.bodis[i - 1] as HTMLElement).offsetLeft;
let y = (this.bodis[i - 1] as HTMLElement).offsetTop;
(this.bodis[i] as HTMLElement).style.left = x + 'px';
(this.bodis[i] as HTMLElement).style.top = y + 'px';
}
}
checkHeadBody(){
for( let i = 1; i this.bodis.length; i++){
let tmp = this.bodis[i] as HTMLElement
if ( this.X === tmp.offsetLeft && this.Y === tmp.offsetTop){
throw new Error('蛇撞自己')
}
}
}
}
export default Snake
GameContro 键盘控制器
import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";
class GameContro{
snake:Snake
food:Food
score_panele:ScorePanel
direction: string = ""
is_live = true
constructor() {
this.snake= new Snake()
this.food = new Food()
this.score_panele = new ScorePanel()
this.init()
}
init(){
document.addEventListener("keydown", this.keyDownHandler.bind(this))
this.run()
}
keyDownHandler(event:KeyboardEvent){
let up_key = ["ArrowUp", "Up", "w", "W"]
let down_key = ["ArrowDown", "Down", "s", "S"]
let right_key = ["ArrowRight", "Right", "d", "D"]
let left_key = ["ArrowLeft", "Left", "a", "A"]
if(up_key.indexOf(event.key) >= 0){
if(this.direction === "Down" && this.snake.bodis.length !== 1){}else{
this.direction = "Up"
}
}
if(down_key.indexOf(event.key) >= 0){
if(this.direction === "Up" && this.snake.bodis.length !== 1){}else{
this.direction = "Down"
}
}
if(right_key.indexOf(event.key) >= 0){
if(this.direction === "Left" && this.snake.bodis.length !== 1){}else{
this.direction = "Right"
}
}
if(left_key.indexOf(event.key) >= 0){
if(this.direction === "Right" && this.snake.bodis.length !== 1){}else{
this.direction = "Left"
}
}
}
checkEat(X:number, Y:number){
if((X === this.food.X) && (Y === this.food.Y)){
console.log('吃到食物')
this.food.change()
this.score_panele.addScore()
this.snake.addBody()
}
}
run(){
let x = this.snake.X
let y = this.snake.Y
switch (this.direction){
case "ArrowUp":
case "Up":
y -= 10
break
case "Down":
y += 10
break
case "Right":
x += 10
break
case "Left":
x -= 10
break
}
this.checkEat(x, y)
try{
this.snake.X = x
this.snake.Y = y
}catch(e:any){
alert(e.message + 'Game Over')
this.is_live = false
}
this.is_live && setTimeout(this.run.bind(this), 300 - (this.score_panele.level - 1) * 30)
}
}
export default GameContro;
main.ts
import './style/main.less'
import GameContro from "./mod/GameContro";
new GameContro()
最终效果
原文地址: typescript 学习记录 - 练习项目 - 贪食蛇