7. Schreiben Sie mit ts ein kleines Schlangenspiel

Ich habe die Grundlagen von ts bereits in mehreren Artikeln gelernt. Heute werden wir ts verwenden, um ein kleines Schlangenspiel abzuschließen.

Spiel-Teardown

Wir werden unsere Aufgabe kurz zerlegen und analysieren.

  1. Zuerst sollten wir ein Fenster haben, das wir Bildschirm nennen. Lassen Sie die Schlange sich im Inneren bewegen, also sollten wir darüber nachdenken, eine große Kiste als Karte zu entwerfen. Unter Berücksichtigung von Nahrung und Schlangenzeichnung können wir canvasdies erreichen.
  2. Zweitens werden wir auch zufällig Nahrung auf der Karte platzieren (Sie können auch überlegen, ob Nahrung auf den Körperknoten der Schlange erscheinen soll, was in diesem Artikel nicht berücksichtigt wird), sodass wir höchstwahrscheinlich eine Klasse erstellen, die zum Erstellen verwendet wird ein zufälliger Block. Das ist Essen.
  3. Dann betrachten wir die Schlange. Die Schlange sollte am Anfang auch ein zufälliger Block sein und dann Nahrung fressen und durch Bewegung wachsen.

Code

Als Nächstes werden wir basierend auf der obigen Demontage eine detaillierte Anforderungssortierung und Codeimplementierung durchführen.
Bildschirmimplementierung
Die Bildschirmimplementierung ist die einfachste. Wir haben uns entschieden, Leinwand zum Zeichnen von Futter und Schlangen zu verwenden, sodass wir direkt ein Leinwand-Tag als Bildschirm erstellen können.

<canvas width="500" height="500"></canvas>

Lebensmittelverwirklichung

  • Als nächstes überlegen wir, wie Essen umgesetzt werden soll. Da Sie sich entschieden haben, Lebensmittel auf Leinwand zu zeichnen, ist es am einfachsten, die Lebensmittel in einem Rechteck zu zeichnen. Das Zeichnen eines Rechtecks ​​erfordert vier Parameter, nämlich die Koordinaten des Startpunkts sowie die Breite und Höhe. Wir setzen die Breite und Höhe des Lebensmittels auf 10, sodass die einzige Unsicherheit die Koordinaten des Startpunkts sind. Diese Koordinate bestimmt, wo auf dem Bildschirm er erscheinen wird.

  • Es ist auch zu beachten, dass seine Startposition auf dem Bewegungspfad der Schlange liegen muss. Die Breite unserer Schlange beträgt beispielsweise 10. Wenn der Startpunkt Ihres Futters bei den Koordinaten (11, 11) liegt, kann er es nicht essen auf einmal. Dieses Essen.
    Fügen Sie hier eine Bildbeschreibung ein
    Daher sollten die Koordinaten des Lebensmittels ein Vielfaches von 10 sein und dürfen die Grenzen des Bildschirms nicht überschreiten.

  • Wir müssen auch berücksichtigen, dass Lebensmittel nach dem Verzehr automatisch verschwinden sollten, sodass die Schlangenklasse auch über eine Löschmethode verfügen sollte, um sich selbst zu löschen.

Codeanzeige

class Drop {
    
    
  width: number = 10
  height: number = 10
  x: number
  y: number
  color: string
  constructor(
    x: number = Math.floor(Math.random() * 49) * 10,
    y: number = Math.floor(Math.random() * 49) * 10,
    color: string = 'black'
  ) {
    
    
    this.color = color
    this.x = x
    this.y = y
  }
  del() {
    
    
    const ctx: CanvasRenderingContext2D = canvasEle.getContext('2d')!
    ctx.clearRect(this.x, this.y, this.width, this.height)
  }
}

Die Implementierung von Snake
Die Implementierung von Snake ist relativ komplizierter.

  • Überlegen wir uns zunächst, wie der Körper der Schlange aussehen soll. Damit sie sich flexibel drehen kann, besteht die einfachste Möglichkeit darin, dass ihr Körper aus einzeln aneinandergefügten Rechtecken besteht. In diesem Fall können wir die obige Lebensmittelklasse direkt verwenden, weshalb ich Dropstattdessen die obige Klasse benannt Foodund der Klasse Farben hinzugefügt habe, um Schlangen von Lebensmitteln zu unterscheiden.
  • Als nächstes dachten wir, dass es einen Container geben sollte, um diese Rechtecke der Reihe nach zu speichern, da die Schlange aus mehreren Rechtecken besteht, also haben wir ein Array listzum Speichern der Körperdaten definiert.
    class Snake {
          
          
    	list: Array<Drop>
    	constructor() {
          
          
    		this.list = [new Drop(250, 250, 'red')]
    	}
    	
    }
    
    Wir lassen ihn im Mittelpunkt der Karte spawnen und verwenden Rot, um ihn vom Futter zu unterscheiden.
  • Als nächstes denken wir über Mobilitätsmethoden nach. Wenn sich die Schlange bewegt, muss sie zunächst die Methode bestätigen. Wir können während der Initialisierung ein Richtungsattribut und einen Standardrichtungswert festlegen. Der nächste Schritt besteht darin, sich in die Richtung zu bewegen. Wie bewegt man sich? Wenn Sie einfach die Übersetzung verwenden, werden Sie feststellen, dass sich die Schlange anscheinend nicht flexibel drehen kann und sich der Körper der Schlange nicht beugt. Zu diesem Zeitpunkt müssen wir unser Denken ändern. Da die Schlange aus Rechtecken besteht, müssen wir nur die Rechtecke im Inneren kontrollieren. Natürlich geht es nicht darum, die Verschiebung des Rechtecks ​​im Inneren zu steuern, sondern darum, das Rechteck hinzuzufügen und zu löschen. Stellen Sie sich vor, wenn sich die Schlange um ein Raster nach oben bewegt (hier legen wir das Grundraster auf 10 x 10 Einheiten fest), bedeutet dies, dass wir 10 vom y-Wert der Startpunktkoordinate dieses Rechtecks ​​​​subtrahieren, sodass wir direkt eine Schlange erstellen Kopffeld? Subtrahieren Sie 10 vom Y-Wert der Startpunktkoordinate des Felds und löschen Sie dann direkt das letzte Feld der Schlange. Kann dies als Verschieben eines Frames angesehen werden?
    Fügen Sie hier eine Bildbeschreibung einNatürlich müssen wir auch die Situation beim Essen berücksichtigen. In diesem Fall müssen wir das Schwanzrechteck nicht löschen. Dann wird unsere Klasse so ergänzt
    class Snake {
          
          
      list: Array<Drop>
      direction: string
      constructor(direction: string = 'ArrowUp', speed: number = 100) {
          
          
        this.list = [new Drop(250, 250, 'red')]
        this.direction = direction
      }
      move() {
          
          
        let newHeader = JSON.parse(JSON.stringify(this.list[0]))
        const {
          
           x: newHeaderX, y: newHeaderY } = newHeader
        const {
          
           x: foodX, y: foodY } = food
        let isEatFood: boolean = false
        if (newHeaderX === foodX && foodY === newHeaderY) {
          
          
          isEatFood = true
        }
        switch (this.direction) {
          
          
          case 'ArrowUp':
            newHeader.y -= 10
            break
          case 'ArrowDown':
            newHeader.y += 10
            break
          case 'ArrowLeft':
            newHeader.x -= 10
            break
          case 'ArrowRight':
            newHeader.x += 10
            break
        }
        this.addHead(newHeader)
        // 判断是否吃到食物
        if (isEatFood) {
          
          
          food.del()
          food = new Drop()
          renderDorp(food)
        } else {
          
          
          this.delFooter()
        }
      }
      addHead(dorp: Drop) {
          
          
    	this.list.unshift(dorp)
      }
      delFooter() {
          
          
    	const endDrop: Drop = this.list.pop()!
        const {
          
           x, y, width, height } = endDrop
        const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!
        ctx.clearRect(x, y, width, height)
      }
    }
    
  • Ich sollte auch einige Sondersituationen berücksichtigen, z. B. ob das Bewegen an den Bildschirmrand meinen eigenen Körper frisst. Wir fügen ein neues Statusattribut hinzu, um festzustellen, ob er draußen ist, und füllen diese Methode weiterhin aus.
    class Snake {
          
          
      list: Array<Drop>
      direction: string
      isOut: boolean
      constructor(direction: string = 'ArrowUp', speed: number = 100) {
          
          
        this.list = [new Drop(250, 250, 'red')]
        this.direction = direction
        this.boolean = false
      }
      move() {
          
          
        let newHeader = JSON.parse(JSON.stringify(this.list[0]))
        const {
          
           x: newHeaderX, y: newHeaderY } = newHeader
        const {
          
           x: foodX, y: foodY } = food
        let isEatFood: boolean = false
        if (newHeaderX === foodX && foodY === newHeaderY) {
          
          
          isEatFood = true
        }
        if (this.direction) {
          
          
        }
        switch (this.direction) {
          
          
          case 'ArrowUp':
            newHeader.y -= 10
            break
          case 'ArrowDown':
            newHeader.y += 10
            break
          case 'ArrowLeft':
            newHeader.x -= 10
            break
          case 'ArrowRight':
            newHeader.x += 10
            break
        }
        // 是否吃到自己
        const isEatSelf = this.list.some(({
          
           x, y }) => {
          
          
          if (x === newHeader.x && y === newHeader.y) {
          
          
            return true
          }
        })
        if (isEatSelf) {
          
          
          alert('吃到自己了!')
          return 
        }
        this.addHead(newHeader)
        // 判断是否吃到食物
        if (isEatFood) {
          
          
          food.del()
          food = new Drop()
          renderDorp(food)
        } else {
          
          
          this.delFooter()
        }
    
        // 判断是否达到边界
        if (
          newHeaderX > 500 ||
          newHeaderY > 500 ||
          newHeaderX < 0 ||
          newHeaderY < 0
        ) {
          
          
          return alert('撞墙了!')
        }
        renderDorp(this.list)
      }
      addHead(dorp: Drop) {
          
          
        this.list.unshift(dorp)
      }
      delFooter() {
          
          
        const endDrop: Drop = this.list.pop()!
        const {
          
           x, y, width, height } = endDrop
        const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!
        ctx.clearRect(x, y, width, height)
      }
    }
    

Schlangen und Nahrung rendern
Wir haben die Klassen für Nahrung und Schlangen geschrieben, aber wir haben sie noch nicht wirklich auf die Leinwand gezeichnet. Als nächstes verwenden wir die Überladung von ts, um die Rendering-Klasse zu zeichnen.

// 创建渲染函数
function renderDorp(dorp: Drop): void
function renderDorp(dorps: Array<Drop>): void
function renderDorp(dorps: Drop | Array<Drop>) {
    
    
  if (Array.isArray(dorps)) {
    
    
    dorps.forEach((element: Drop) => {
    
    
      const {
    
     x, y, width, height, color } = element
      const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!

      ctx.fillStyle = color
      ctx.fillRect(x, y, width, height)
    })
  } else {
    
    
    const {
    
     x, y, width, height, color } = dorps
    const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!
    ctx.fillStyle = color
    ctx.fillRect(x, y, width, height)
  }
}

Tastaturüberwachung
Wenn wir die Pfeiltasten verwenden, um die Bewegung der Schlange zu steuern, müssen wir Tastaturereignisse überwachen. Es ist zu beachten, dass wir uns bei einer Körperlänge von 1 normalerweise nach Belieben bewegen können, z. B. direkt von rechts nach links oder von oben nach unten. Wenn die Körperlänge jedoch nicht 1 beträgt, haben wir die Definition von Kopf und Schwanz. , es sollte sich nicht beliebig nach oben und unten oder nach links und rechts bewegen. Schließlich hat er keine vordere und hintere Lokomotive wie ein Zug.

window.addEventListener('keydown', function (e) {
    
    
 const {
    
     code } = e
  const keys: string[] = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']
  if (keys.includes(code)) {
    
    
    if (snake.list.length === 1) {
    
    
      snake.direction = code
      return
    }
    if (snake.direction === 'ArrowUp' && code === 'ArrowDown') {
    
    
      return
    }
    if (snake.direction === 'ArrowDown' && code === 'ArrowUp') {
    
    
      return
    }
    if (snake.direction === 'ArrowLeft' && code === 'ArrowRight') {
    
    
      return
    }
    if (snake.direction === 'ArrowRight' && code === 'ArrowLeft') {
    
    
      return
    }
    snake.direction = code
  }
})

Fügen Sie abschließend den vollständigen Implementierungscode hinzu

const canvasEle = document.querySelector('canvas')!
let food: Drop
let snake: Snake
class Drop {
    
    
  width: number = 10
  height: number = 10
  x: number
  y: number
  color: string
  constructor(
    x: number = Math.floor(Math.random() * 49) * 10,
    y: number = Math.floor(Math.random() * 49) * 10,
    color: string = 'black'
  ) {
    
    
    this.color = color
    this.x = x
    this.y = y
  }
  del() {
    
    
    const ctx: CanvasRenderingContext2D = canvasEle.getContext('2d')!
    ctx.clearRect(this.x, this.y, this.width, this.height)
  }
}

class Snake {
    
    
  list: Array<Drop>
  direction: string
  isOut: boolean
  constructor(direction: string = 'ArrowUp', speed: number = 100) {
    
    
    this.list = [new Drop(250, 250, 'red')]
    this.direction = direction
    this.isOut = false
  }
  move() {
    
    
    let newHeader = JSON.parse(JSON.stringify(this.list[0]))
    const {
    
     x: newHeaderX, y: newHeaderY } = newHeader
    const {
    
     x: foodX, y: foodY } = food
    let isEatFood: boolean = false
    if (newHeaderX === foodX && foodY === newHeaderY) {
    
    
      isEatFood = true
    }
    if (this.direction) {
    
    
    }
    switch (this.direction) {
    
    
      case 'ArrowUp':
        newHeader.y -= 10
        break
      case 'ArrowDown':
        newHeader.y += 10
        break
      case 'ArrowLeft':
        newHeader.x -= 10
        break
      case 'ArrowRight':
        newHeader.x += 10
        break
    }
    // 是否吃到自己
    const isEatSelf = this.list.some(({
    
     x, y }) => {
    
    
      if (x === newHeader.x && y === newHeader.y) {
    
    
        return true
      }
    })
    if (isEatSelf) {
    
    
      this.isOut = true
      return alert('吃到自己了!')
    }
    this.addHead(newHeader)
    // 判断是否吃到食物
    if (isEatFood) {
    
    
      food.del()
      food = new Drop()
      renderDorp(food)
    } else {
    
    
      this.delFooter()
    }

    // 判断是否达到边界
    if (
      newHeaderX > 500 ||
      newHeaderY > 500 ||
      newHeaderX < 0 ||
      newHeaderY < 0
    ) {
    
    
      this.isOut = true
      return alert('撞墙了!')
    }
    renderDorp(this.list)
  }
  addHead(dorp: Drop) {
    
    
    this.list.unshift(dorp)
  }
  delFooter() {
    
    
    const endDrop: Drop = this.list.pop()!
    const {
    
     x, y, width, height } = endDrop
    const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!
    ctx.clearRect(x, y, width, height)
  }
}

// 创建渲染函数
function renderDorp(dorp: Drop): void
function renderDorp(dorps: Array<Drop>): void
function renderDorp(dorps: Drop | Array<Drop>) {
    
    
  if (Array.isArray(dorps)) {
    
    
    dorps.forEach((element: Drop) => {
    
    
      const {
    
     x, y, width, height, color } = element
      const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!

      ctx.fillStyle = color
      ctx.fillRect(x, y, width, height)
    })
  } else {
    
    
    const {
    
     x, y, width, height, color } = dorps
    const ctx: CanvasRenderingContext2D = canvasEle!.getContext('2d')!
    ctx.fillStyle = color
    ctx.fillRect(x, y, width, height)
  }
}

;(function () {
    
    
  food = new Drop()
  snake = new Snake()
  renderDorp(food)
  let timer = setInterval(() => {
    
    
    snake.move()
    if (snake.isOut) {
    
    
      clearInterval(timer)
    }
  }, 100)
  window.addEventListener('keydown', function (e) {
    
    
    const {
    
     code } = e
    const keys: string[] = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']
    if (keys.includes(code)) {
    
    
      if (snake.list.length !== 1) {
    
    
        if (snake.direction === 'ArrowUp' && code === 'ArrowDown') {
    
    
          return
        }
        if (snake.direction === 'ArrowDown' && code === 'ArrowUp') {
    
    
          return
        }
        if (snake.direction === 'ArrowLeft' && code === 'ArrowRight') {
    
    
          return
        }
        if (snake.direction === 'ArrowRight' && code === 'ArrowLeft') {
    
    
          return
        }
      }
      snake.direction = code
    }
  })
})()

Dies ist nur eine einfache Version des Schlangeneffekts. Es wurde nicht gründlich getestet. Es wird definitiv Fehler geben. Ich hoffe, Sie können eine Nachricht zur Kommunikation hinterlassen!
Ich werde eine weitere erweiterte Komponentenbibliothek meines eigenen Plug-Ins element-ui starten, das noch verbessert wird. Ich hoffe, Sie werden es unterstützen.

Acho que você gosta

Origin blog.csdn.net/qq_44473483/article/details/135025216
Recomendado
Clasificación