Saturday, February 11, 2012

Game Engine Architecture, now in Scala

In my previous post I discussed game engine architecture, object<->component hierarchy, etc. Here's the sample architecture as the last post, but instead of C++ this version is written in Scala. One thing worth noting, we get identical functionality in this Scala example, with 35% less code!

package MessagingInScala

object GameMessageType extends Enumeration {
  type GameMessageType = Value

  val SetPosition = Value
  val GetPosition = Value
}
import GameMessageType._

class Vector3(var x: Float, var y: Float, var z: Float)

abstract class GameMessage (val destinationID: Int) {  
  def messageType: GameMessageType
}

abstract class PositionMessage (destinationID: Int,
                                var x: Float, var y: Float,
                                var z: Float) extends GameMessage(destinationID) {
}  

class MsgSetPosition(destinationID: Int,
                     x: Float, y: Float,
                     z: Float) extends PositionMessage(destinationID, x, y, z) {
  override val messageType = SetPosition
}
object MsgSetPosition {
  def Apply(destinationID: Int,
            x: Float, y: Float,
            z: Float) = new MsgSetPosition(destinationID, x, y, z)
}

class MsgGetPosition(destinationID: Int) extends PositionMessage(destinationID, 0.0f, 0.0f, 0.0f) {
  val messageType = GetPosition
}
object MsgGetPosition {
  def Apply(destinationID: Int) = new MsgGetPosition(destinationID)
}

abstract class BaseComponent {
  def SendMessage(message: GameMessage) = false
}

class RenderComponent extends BaseComponent {
  override def SendMessage(message: GameMessage): Boolean = {
    message.messageType match {
      case GameMessageType.SetPosition => {
        // Update render mesh position
        println("RenderComponent received SetPosition")
        true // Return value
      }
      case _ => super.SendMessage(message)
    }
  }
}

class Entity(ID: Int) {
  private var Components: List[BaseComponent] = List()
  var position: Vector3 = new Vector3(0.0f, 0.0f, 0.0f)
  val uniqueID: Int = ID

  def AddComponent(component: BaseComponent) {
    Components = component :: Components
  }

  def SendMessage(message: GameMessage): Boolean = {
    message.messageType match {
      case GameMessageType.SetPosition => {
        println("Entity received SetPosition")
        var msgSetPos: MsgSetPosition = message.asInstanceOf[MsgSetPosition]
        position.x = msgSetPos.x
        position.y = msgSetPos.y
        position.z = msgSetPos.z
        PassMessageToComponents(message) // This is also the return value
      }
      case GameMessageType.GetPosition => {
        println("Entity received GetPosition")
        var msgGetPos: MsgGetPosition = message.asInstanceOf[MsgGetPosition]
        msgGetPos.x = position.x
        msgGetPos.y = position.y
        msgGetPos.z = position.z
        PassMessageToComponents(message) // This is also the return value
      }
      case _ => PassMessageToComponents(message) // This is also the return value
    }
  }
  
  def PassMessageToComponents(message: GameMessage): Boolean = {
    var messageHandled = false
    Components.foreach(c => {
      messageHandled |= c.SendMessage(message)
    })
    
    messageHandled
  }
}

object Entity {
  var nextUUID: Int = 0  
  def apply() = new Entity(nextUUID + 1)
}

class SceneManager {
  // You don't need to type the entire HashMap path like this, I'm
  // doing this so the reader understands this is not a Java HashMap
  var entities: Map[Int, Entity] = Map.empty[Int, Entity]
  
  def SendMessage(message: GameMessage): Boolean = {
    if ( entities.contains(message.destinationID) ) {
      entities(message.destinationID).SendMessage(message)
    } else {
      false
    }
  }
  
  def CreateEntity(): Entity = {
    val newEntity: Entity = Entity()
    entities += newEntity.uniqueID -> newEntity
    newEntity
  }
}


object Main extends App {
  val sceneMgr: SceneManager = new SceneManager
  
  val testEntity = sceneMgr.CreateEntity()
  val testRenderComp = new RenderComponent
  testEntity.AddComponent(testRenderComp)
  
  val msgSetPos: MsgSetPosition = new MsgSetPosition(testEntity.uniqueID, 1.0f, 2.0f, 3.0f)
  sceneMgr.SendMessage(msgSetPos)
  println("Position set to (1, 2, 3) on entity with ID " + testEntity.uniqueID)  

  println("Retreiving position from object with ID: " + testEntity.uniqueID)

  val msgGetPos: MsgGetPosition = new MsgGetPosition(testEntity.uniqueID)
  sceneMgr.SendMessage(msgGetPos)
  println("X: " + msgGetPos.x)
  println("Y: " + msgGetPos.y)
  println("Z: " + msgGetPos.z)
}

1 comment: