6
\$\begingroup\$

This simple game is built on the ECS described here: Minimal entity component system, take 2

local ecs = require 'ecs'
local ui = require 'ui'

local entities = {}

local velocitySystem = ecs.createSystem(
  { 'position', 'velocity' },
  function (entity, pos, vel)
    pos.x = pos.x + vel.vx
    pos.y = pos.y + vel.vy
  end)

local rectangleRenderingSystem = ecs.createSystem(
  { 'position', 'rectangle' },
  function (entity, pos, rect)
    ui.drawRect(pos, rect)
  end)

local aiSystem = ecs.createSystem(
  { 'position', 'speed', 'ai' },
  function (entity, pos, speed)
    local closestBallPos
    for target, posB in ecs.each(entities, { 'position', 'ball' }) do
      if (not closestBallPos) or (posB.x > closestBallPos.x) then
        closestBallPos = posB
      end
    end
    pos.y = pos.y + (closestBallPos.y - pos.y) * speed
  end)

local inputSystem = ecs.createSystem(
  { 'position', 'player' },
  function (entity, pos)
    pos.y = ui.mouseY or 0
  end)

local outOfBoundsSystem = ecs.createSystem(
  { 'position', 'rectangle', 'velocity', 'ball' },
  function (entity, pos, rect, vel)
    if pos.x <= 0 then
      print('Right player scored')
      pos.x = 50
    elseif pos.x >= 100 then
      print('Left player scored')
      pos.x = 50
    end
  end)

local bounceSystem = ecs.createSystem(
  { 'position', 'rectangle', 'velocity', 'ball' },
  function (entity, pos, rect, vel)
    if pos.y <= 0 or pos.y >= 100 then
      vel.vy = vel.vy * -1
    end
  end)

local function checkCollision(posA, rectA, posB, rectB)
  local halfHeightA, halfHeightB = rectA.h / 2, rectB.h / 2
  local halfWidthA, halfWidthB = rectA.w / 2, rectB.w / 2
  if (posA.y + halfHeightA <= posB.y - halfHeightB) or 
      (posA.y - halfHeightA >= posB.y + halfHeightB) or
      (posA.x + halfWidthA <= posB.x - halfWidthB) or 
      (posA.x - halfWidthA >= posB.x + halfWidthB) then
    return false
  end
  return true
end

local collisionSystem = ecs.createSystem(
  { 'collidable', 'position', 'rectangle', 'velocity' },
  function (entity, col, pos, rect, vel)
    for target, posB, rectB in ecs.each(entities,
        { 'position', 'rectangle', 'solid' }) do
      if entity == target then next() end
      if checkCollision(pos, rect, posB, rectB) then
        vel.vx = vel.vx * -1
        vel.vy = vel.vy * -1
      end
    end
  end)

local ball = {
  ball = true,
  collidable = true,
  position = { x = 50, y = 33 },
  velocity = { vx = 1, vy = 1 },
  rectangle = { w = 2, h = 2 },
}

local leftPaddle = {
  player = true,
  solid = true,
  position = { x = 0, y = 50 },
  rectangle = { w = 4, h = 10 },
}

local rightPaddle = {
  ai = true,
  solid = true,
  speed = 0.5,
  position = { x = 100, y = 50 },
  rectangle = { w = 4, h = 10 },
}

table.insert(entities, ball)
table.insert(entities, leftPaddle)
table.insert(entities, rightPaddle)

ui.loop(function ()
  velocitySystem(entities)
  rectangleRenderingSystem(entities)
  aiSystem(entities)
  inputSystem(entities)
  outOfBoundsSystem(entities)
  bounceSystem(entities)
  collisionSystem(entities)
end)

I'm wondering if I have the right idea about how I should be using an ECS. If there are problems with the ECS itself, please address it in linked question rather than here.

The "ui" script that's required at the top is some uninteresting OpenGL/GLUT stuff using luaglut. I can post it if necessary, but it should be pretty clear what it's doing.

I realize there are lots of magic numbers. I don't care, it's just a throwaway project to make sure the ECS is doing its job. All of the coordinates and measurements are in percents, so just pretend I made constants named FIFTY_PERCENT and so on.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.