JavaScript Feld in Klasse undefined obwohl im Konstruktor gesetzt

jokakilla

Lt. Junior Grade
Registriert
Dez. 2007
Beiträge
308
Hallo zusammen,
ich versuche gerade ein wenig in JavaScript rein zu kommen und habe mir als einfaches Beispiel Snake implementiert.
Nun wollte ich die Spielelogik in eine Klasse "Logic" auslagern. Das Snake Objekt wird außen erzeugt und über den Konstruktor an das Logic Objekt übergeben. Ich sehe dass die this.snake danach gesetzt ist. Direkt nach dem Konstruktor wird logic.start() aufgerufen. Darin wird ein Interval für die Spielezüge gestaret. executePlayStep wird vom Interval getriggert. Im executePlayStep ist dann this.snake undefined. Das gleiche gilt übrigens für apples in spawnApple. Es scheint bei allen Methoden die aus einem Interval aufgerufen werden so zu sein. Ich hoffe jemand hat eine zündende Idee.

Javascript:
class Logic {
    score = 0
    apples = []
    paused = false

    constructor(snake, gameAreaWidth, gameAreaHeight, window, gameOverFunction) {
        this.snake = snake
        this.gameAreaWidth = gameAreaWidth
        this.gameAreaHeight = gameAreaHeight
        this.window = window
        this.gameOverFunction = gameOverFunction
        this.score = 0
        this.apples = []
        this.paused = false
    }

    executePlayStep() {
        console.debug( "executePlayStep " + this.snake )
        this.snake.move()
        this.detectWallCollisionAndSetToOtherSide()
        this.eatAppleNearby()
        if( this.snake.bitTail() ) {
            this.stop()
            this.gameOverFunction()
        }
    }

    start() {
        this.playing = true
        this.playInterval = this.window.setInterval( this.executePlayStep, 20 )
        this.spawnAppleInterval = this.window.setInterval( this.spawnApple, 500 )
    }

    stop() {
        this.playing = false
        this.window.clearInterval( this.playInterval )
        this.window.clearInterval( this.spawnAppleInterval )
    }

    togglePaused() {
        if(this.paused) {
            this.paused = false
            this.start()
        }
        else {
            if(this.playing) {
                this.paused = true
                this.stop()
            }
        }
    }

    reset() {
        //TODO
    }

    isActive() {
        return this.playing
    }

    onDirectionChanged( newDirection ) {
        if( newDirection == direction.left && this.snake.currentDirection == direction.right ||
            newDirection == direction.right && this.snake.currentDirection == direction.left ||
            newDirection == direction.up && this.snake.currentDirection == direction.down ||
            newDirection == direction.down && this.snake.currentDirection == direction.up ) {
            return
        }
        this.snake.currentDirection = newDirection
    }

    spawnApple() {
        var randomX = Math.floor(Math.random() * this.gameAreaWidth) 
        var randomY = Math.floor(Math.random() * this.gameAreaHeight)
        this.apples.push( new Apple(randomX, randomY))
    }

    eatAppleNearby() {
        var nearByApples = this.apples.filter( apple => {
            var eatDistance = apple.size / 2 + this.snake.headSize / 2
            return Math.abs(apple.x - this.snake.x) < eatDistance && Math.abs(apple.y - this.snake.y) < eatDistance
        })
    
        nearByApples.forEach( nearByApple => {
            const index = this.apples.indexOf(nearByApple)
            if (index > -1) {
                this.apples.splice(index, 1)
            }
            this.snake.grow()
            this.score++
        })
    }

    beamOnOtherSideWhenBorderCrossed() {
        if( this.snake.x > this.gameAreaWidth ) {
            this.snake.x = 0
        }
        else if( this.snake.x < 0 ) {
            this.snake.x = this.gameAreaWidth
        }
        else if( this.snake.y > this.gameAreaHeight ) {
            this.snake.y = 0
        }
        else if( this.snake.y < 0 ) {
            this.snake.y = this.gameAreaHeight
        }
    }
  }
 
Die Bindung der Funktion executePlayStep ist dein Problem. Ruf in der Funktion einfach mal console.log(this); auf, dann siehst du das Problem.

Lösung: ein Closure erstellen: setInterval(() => this.executePlayStep(), 20);.
Alternative für altes JavaScript: setInterval(this.executePlayStep.bind(this), 20);.

PS: Statt setInterval solltest du besser requestAnimationFrame nutzen.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: jokakilla
Top! Danke Dir. Mit dem Closure funktioniert es jetzt. Dann schaue ich mir mal das requestAnimationFrame an.
Ergänzung ()

Habe ich das mit dem requestAnimationFrame richtig verstanden?

Im Moment habe ich zwei Intervalle. Eins für das Rendering und eins für die Spielelogik. Das Rendering läuft mit 60fps und aktualisiert die GUI. Die Spielelogik wird seltener getriggert und verändert nur den Spielzustand.

Das Rendering Interval könnte ich mir mit dem requestAnimationFrame sparen weil ich einfach das Render requesten kann wenn sich der Spielzustand geändert hat. Dadurch spart man vermutlich sehr häufiges neu Rendern des gleichen Spielzustands?
 
Zuletzt bearbeitet:
requestAnimationFrame führt dein callback immer dann aus, wenn der Browser dabei ist, im nächsten "Augenblick" die Seite neu zu rendern. Läuft dein Spiel mit 30 FPS (z.B. weil Grafikkarte zu langsam), so wird das ganze auch nur 30 mal pro Sekunde aufgerufen. Läuft es mit 144 FPS (z.B. weil 144Hz Monitor), dementsprechend öfter.

Genau, das was du da sagst, ist richtig. Einfach immer, wenn es ein neuen Spielzustand gibt, aber eben maximal so schnell, wie der Browser rendern kann.

Noch besser wäre es jetzt wohl, wenn du das Berechnen des neuen Zustands immer dann machst, wenn der Browser es auch rendern kann, ansonsten ist es ja umsonst berechnet.
 
Zurück
Oben