Skip to content

Instantly share code, notes, and snippets.

@pablitar
Last active May 27, 2016 03:10
Show Gist options
  • Save pablitar/d99487e83e0682afc466f71ea28e5fae to your computer and use it in GitHub Desktop.
Save pablitar/d99487e83e0682afc466f71ea28e5fae to your computer and use it in GitHub Desktop.
import scala.util.Random
/**
* Created by pablitar on 17/05/16.
*/
//La aplicación de escritorio sería interesante que fuera una app de scala lisa y llana
object PewPewApp extends VainillaApp(new PewPewGame())
//Extender la superclase sería debido a que es probable que haya un ciclo de vida.
//Además, es un contrato fuerte que nos permite que no le compile el programa si no definió lo mínimo
//que necesitamos para crear un juego. gameTitle y createInitialScene serían métodos abstractos
class PewPewGame extends VainillaGame {
override def gameTitle = "Pew Pew"
override def createInitialScene = new PewPewGameScene(this)
/*No haría falta definir esto. Por defecto habría una configuración básica
override def configure = {
screenSize = (1024, 768)
}*/
//El Game serviría para guardar "estado global"
def highScores = loadHighScoresFromFile()
}
//En principio, no necesitamos loaders ni nada por el estilo. Si hubiera esa necesidad, estaría bueno manejarlo a nivel
//de escena
object Resources {
//Muchas de las apariencias en principio serían inmutables
lazy val playerPhase1 = new Sprite("playerPhase1.png")
}
//Game scene también tendría su ciclo de vida. A su vez, idea interesante sería que escena sea un tipo de Game Component,
//Para que se puedan componer
class PewPewGameScene(val game: PewPewGame) extends VainillaGameScene {
val playerShip = new PlayerShip()
//Por defecto al agregar un componente lo agrego en el world space. Un espacio vendría a ser como
//un sistema de coordenadas. Cada espacio tiene su propia cámara. En principio, una escena tiene
//dos espacios, pero sería interesante permitir agregar más.
this.addComponents(
playerShip,
new StarFieldBackground(this.cameraFor(this.worldSpace)),
new EnemySpawner(this),
//Este componente quizás lo proveemos nosotros, o quizás lo desarrollan los alumnos. Lo que haría sería hacer
//que la posición de la cámara cambie con la posición de la nave.
new CameraFollow(playerShip, this.cameraFor(this.worldSpace))
)
//Este en cambio lo agrego en el screen space. Esto haría que su posición sea fija en la pantalla.
this.addComponent(new Score(this), this.screenSpace)
}
//Ejemplo de un componente con una apariencia no estándar, y estructura no estándar
class StarFieldBackground(camera: Camera) extends VainillaGameScene {
val amountOfStars = 500
//Genera estrellas posicionadas alrededor de la cámara
var stars:List[Star] = generateStarsFor(camera)
override val appearance = new StarFieldAppearance(this)
override def update(state: FrameState) = {
stars.foreach { aStar =>
star.update(state.delta)
}
removeStarsOutOfBounds(camera)
addNewStarsIfNeeded(camera)
}
//Los game components definirían render y update. Update por defecto no haría nada. Render por defecto delega
//a la apariencia
}
class StarFieldAppearance(starFieldBackground: StarFieldBackground) extends Appearance {
//Esto obviamente estaría muy atado a LibGDX... hay que ver qué se provee, pero la gracia es
//recibir lo necesario para poder determinar si tiene que dibujarse o no, y que pueda dibujarse.
//La idea es que exista un trait Renderable. Los GameComponents por defecto extenderían de Renderable
//(o quizás no, quizás con Scala se puede hacer algo elegante en donde no todos los GameComponent extiendan de Renderable)
//Un Renderable permitiría obtener una posición en pantalla a partir de una cámara, y determinar si está visible o no
//(quizás entender bounds, o algo así, que permita determinar si está visible o no)
override def render(what: Renderable, camera: Camera, canvas: Canvas): Unit = {
//En este caso, el renderable no se usa porque se delega a las estrellas.
starFieldBackground.stars.foreach({ aStar =>
aStar.render(camera, canvas)
})
}
}
//Una estrella sería como una partícula: Las partículas tienen posición y velocidad, y quizás algo más.
class Star extends Renderable with Speed {
val size = Random.nextInt(4) + 1
override def appearance = new RectangularAppearance(size, size, Color.hsb(randomHue, randomSaturation, randomBrightness))
}
class Score(scene: PewPewGameScene) extends GameComponent {
var currentScore = 0
//Para soportar una especie de binding, estaría bueno que los labels soporten que les pases una
//lambda que obtenga el texto a mostrar. Sería un parámetro by name, o quizás para hacerlo más
//explícito, una función, como está acá abajo
override val appearance = Label(() => this.scoreText)
def scores = scene.game.highScores
def scoreText = s"Score: $currentScore\nHigh Score: ${scores.highest}"
}
class PlayerShip extends GameComponent {
//Radianes por segundo
val rotationSpeed = Math.PI
//Unidades de distancia por segundo
var speed: Vector2D = (0,0)
//Unidades de distancia por segundo al cuadrado
var thrustAcceleration = 100
val rotationKeys = Map(
Key.LEFT -> Rotation.COUNTER_CLOCKWISE,
Key.RIGHT -> Rotation.CLOCKWISE
)
//Todo game component debería definir una appearance. Un appearance podría o no ser un var
override var appearance = Resources.playerPhase1
def thrustVector = {
//this.direction devuelve el versor director, del "frente" del objeto, en función de la matriz de transformación.
//También debería estar definido en GameComponent. Esto tendría el efecto de hacer que la nave acelere en la
//dirección en la que está mirando el jugador
this.thrustAcceleration * this.direction
}
//Esto es una forma propuesta, pero no necesariamente tiene que ser así.
override def update(state: FrameState): Unit = {
//Tener en cuenta que esta función parcial debería ejecutarse una vez por cada evento de input
state.onInput {
case KeyPressed(key) if rotationKeys.contains(key) => {
//Rotate sería un método definido por los Game Components. La idea es que todos los game components
//tengan una matriz de transformación lineal propia, y que los métodos rotate y parecidos encapsulen
//esa complejidad hasta que introduzcamos el concepto
this.rotate(rotationKeys(key))(state.delta * rotationSpeed)
}
case KeyPressed(Key.UP) => {
this.speed += this.thrustVector * state.delta
}
case KeyDown(Key.SPACE) => {
this.firing = true
}
case KeyUp(Key.SPACE) => {
this.firing = false
}
}
if(firing) {
this.cooldownAndFire()
}
//Move movería de acuerdo al vector que le pases por parámetro
//en el sistema de coordenadas padre, que en este caso sería global. Quizás vale la pena contemplar
//la posibilidad de que los game components estén anidados, y en ese caso move sería según el sistema de coordenadas
//del game component padre
//También podría ser que uno le pase un sistema de coordenadas por parámetro, que podría ser un parámetro implícito o algo así
this.move(this.speed * state.delta)
}
}
class EnemySpawner(scene: PewPewGameScene) extends GameComponent {
//Invisible es para los game components que no se renderizan de ninguna manera
override val appearance = Invisible
val spawnTime = 5 //segundos
var elapsedSinceSpawn = 0.0
val spawnPositions:Seq[Vector2D] = List((0,0), (0,333), (323, 257), (111, 275))
def spawnPosition = {
spawnPositions(Random.nextInt(spawnPositions.length))
}
def spawnEnemy() = {
//Add component modifica la lista de components durante un frame. Esto implica que
//la iteración de los elementos tiene que hacerse en una copia de la lista.
//Hay que ver perfomance, pero es posible que con colecciones inmutables esto funcione bien.
scene.addComponent(new EnemyShip(spawnPosition))
}
override def update(state: FrameState): Unit = {
elapsedSinceSpawn += state.delta
if(elapsedSinceSpawn > spawnTime) {
spawnEnemy()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment