471 lines
15 KiB
Lua
471 lines
15 KiB
Lua
|
local hazel = require "hazel"
|
||
|
local constants = require "constants"
|
||
|
local math = require "math"
|
||
|
local vmath = require "vmath"
|
||
|
local content = require "content"
|
||
|
|
||
|
---@class ECS
|
||
|
local _M = {}
|
||
|
|
||
|
---@class Entity
|
||
|
---@field name string
|
||
|
---@field component table<Component>
|
||
|
local Entity = { name = "", components = {}}
|
||
|
|
||
|
---@param component Component
|
||
|
function Entity.SetComponent(self, component)
|
||
|
if not component then
|
||
|
return
|
||
|
end
|
||
|
---@type Component
|
||
|
local oldComp = self.components[component:GetName()]
|
||
|
if oldComp then
|
||
|
oldComp.parent = nil
|
||
|
hazel.Log.Logw(string.format("component %s already exists, replace it", oldComp:GetName()))
|
||
|
end
|
||
|
component.parent = self
|
||
|
self.components[component:GetName()] = component
|
||
|
end
|
||
|
|
||
|
---@return Component|nil
|
||
|
---@param type string
|
||
|
function Entity.GetComponent(self, type)
|
||
|
if not type then
|
||
|
return nil
|
||
|
end
|
||
|
return self.components[type]
|
||
|
end
|
||
|
|
||
|
---@param type string
|
||
|
function Entity.RemoveComponent(self, type)
|
||
|
self.components[type] = nil
|
||
|
end
|
||
|
|
||
|
_M.Entity = Entity
|
||
|
|
||
|
---@return Entity
|
||
|
---@param name string
|
||
|
function _M.CreateEntity(name)
|
||
|
local o = {name = name, components = {}}
|
||
|
setmetatable(o, {__index = Entity})
|
||
|
return o
|
||
|
end
|
||
|
|
||
|
---@param self Entity
|
||
|
function Entity.Update(self)
|
||
|
---@param v Component
|
||
|
for _, v in pairs(self.components) do
|
||
|
if v:IsActive() and v.Update then
|
||
|
v:Update()
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---@class Component
|
||
|
local Component = {
|
||
|
---@param self Component
|
||
|
---@return boolean
|
||
|
IsActive = function(self)
|
||
|
return self.isActive
|
||
|
end,
|
||
|
|
||
|
---@param self Component
|
||
|
Disable = function(self)
|
||
|
self.isActive = false
|
||
|
end,
|
||
|
|
||
|
---@param self Component
|
||
|
Enable = function(self)
|
||
|
self.isActive = true
|
||
|
end,
|
||
|
|
||
|
---@param self Component
|
||
|
---@return string
|
||
|
GetName = function(self)
|
||
|
return self.name
|
||
|
end,
|
||
|
|
||
|
---@param self Component
|
||
|
---@return Entity
|
||
|
GetParent = function(self)
|
||
|
return self.parent
|
||
|
end,
|
||
|
}
|
||
|
|
||
|
---@return Component
|
||
|
function _M.CreateComponent(o)
|
||
|
local o = o or {}
|
||
|
setmetatable(o , {__index = Component})
|
||
|
return o
|
||
|
end
|
||
|
|
||
|
---@class ComponentType
|
||
|
local ComponentType = {
|
||
|
Transform = "Transform",
|
||
|
Image = "Image",
|
||
|
Controller = "Controller",
|
||
|
RoleProp = "RoleProp",
|
||
|
HpShow = "HpShow",
|
||
|
Gun = "Gun",
|
||
|
Bullet = "Bullet",
|
||
|
Direction = "Direction",
|
||
|
AI = "AI",
|
||
|
ColliBox = "ColliBox",
|
||
|
Invincible = "Invincible",
|
||
|
}
|
||
|
|
||
|
_M.ComponentType = ComponentType
|
||
|
|
||
|
---@class TransformComponent:Component
|
||
|
---@field position Point
|
||
|
---@field size Point
|
||
|
---@field flip number
|
||
|
|
||
|
---@return TransformComponent
|
||
|
---@param pos Point
|
||
|
---@param size Point
|
||
|
---@param flip number|nil
|
||
|
function _M.CreateTransformComponent(pos, size, flip)
|
||
|
local o = { isActive = true, name = ComponentType.Transform, position = pos, size = size, flip = flip or Flip.None, parent = nil }
|
||
|
o.Update = function () end
|
||
|
return _M.CreateComponent(o)
|
||
|
end
|
||
|
|
||
|
---@class ImageComponent:Component
|
||
|
---@field tilesheet TileSheet
|
||
|
---@field row number
|
||
|
---@field col number
|
||
|
|
||
|
---@return ImageComponent
|
||
|
---@param tilesheet TileSheet
|
||
|
---@param col number
|
||
|
---@param row number
|
||
|
function _M.CreateImageComponent(tilesheet, col, row)
|
||
|
local o = { isActive = true, name = ComponentType.Image, tilesheet = tilesheet, col = col, row = row, parent = nil }
|
||
|
|
||
|
---@param self ImageComponent
|
||
|
o.Update = function(self)
|
||
|
---@type TransformComponent
|
||
|
local transform = self:GetParent():GetComponent(ComponentType.Transform)
|
||
|
if tilesheet then
|
||
|
tilesheet:Draw(self.col or 0, self.row or 0,
|
||
|
hazel.CreateRect(transform.position.x, transform.position.y, transform.size.x, transform.size.y),
|
||
|
transform.flip)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return _M.CreateComponent(o)
|
||
|
end
|
||
|
|
||
|
---@class DirectionComponent:Component
|
||
|
---@field direction Point
|
||
|
|
||
|
function _M.CreateDirectionComponent(startRow)
|
||
|
local o = { isActive = true, name = ComponentType.Direction, direction = hazel.CreatePos(0, 0), startRow = startRow, parent = nil }
|
||
|
|
||
|
---@param self DirectionComponent
|
||
|
---@param dir Point
|
||
|
o.SetDir = function(self, dir)
|
||
|
self.direction = dir
|
||
|
end
|
||
|
|
||
|
---@param self DirectionComponent
|
||
|
o.Update = function(self)
|
||
|
local image = self:GetParent():GetComponent(ComponentType.Image)
|
||
|
if not image then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local absX = math.abs(self.direction.x)
|
||
|
local absY = math.abs(self.direction.y)
|
||
|
if self.direction.y <= 0 and absX <= absY then
|
||
|
image.col = 0
|
||
|
image.row = 1 + self.startRow
|
||
|
end
|
||
|
if self.direction.y > 0 and absX <= absY then
|
||
|
image.col = 0
|
||
|
image.row = 0 + self.startRow
|
||
|
end
|
||
|
if self.direction.x < 0 and absY < absX then
|
||
|
image.col = 0
|
||
|
image.row = 2 + self.startRow
|
||
|
end
|
||
|
if self.direction.x > 0 and absY < absX then
|
||
|
image.col = 0
|
||
|
image.row = 3 + self.startRow
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
return _M.CreateComponent(o)
|
||
|
end
|
||
|
|
||
|
---@class ControllerComponent:Component
|
||
|
|
||
|
---@return ControllerComponent
|
||
|
---@param speed number
|
||
|
function _M.CreateControllerComponent()
|
||
|
local o = { isActive = true, name = ComponentType.Controller, parent = nil }
|
||
|
|
||
|
---@param self ControllerComponent
|
||
|
o.Update = function(self)
|
||
|
---@type RolePropComponent
|
||
|
local roleProp = self:GetParent():GetComponent(ComponentType.RoleProp)
|
||
|
local speed = roleProp.speed
|
||
|
|
||
|
---@type TransformComponent
|
||
|
local transform = self:GetParent():GetComponent(ComponentType.Transform)
|
||
|
local elapseTime = hazel.Time.GetElapseTime()
|
||
|
if hazel.IsKeyPressing(hazel.Key.A) then
|
||
|
transform.position.x = transform.position.x - speed * elapseTime
|
||
|
end
|
||
|
if hazel.IsKeyPressing(hazel.Key.D) then
|
||
|
transform.position.x = transform.position.x + speed * elapseTime
|
||
|
end
|
||
|
if hazel.IsKeyPressing(hazel.Key.W) then
|
||
|
transform.position.y = transform.position.y - speed * elapseTime
|
||
|
end
|
||
|
if hazel.IsKeyPressing(hazel.Key.S) then
|
||
|
transform.position.y = transform.position.y + speed * elapseTime
|
||
|
end
|
||
|
|
||
|
---@type Point
|
||
|
local mousePos = hazel.GetGameMousePos()
|
||
|
|
||
|
local playerCenterX = transform.position.x
|
||
|
local playerCenterY = transform.position.y
|
||
|
playerCenterX = playerCenterX + constants.TileSize / 2
|
||
|
playerCenterY = playerCenterY + constants.TileSize / 2
|
||
|
---@type Point
|
||
|
local mouseDir = hazel.CreatePos(mousePos.x - playerCenterX, mousePos.y - playerCenterY)
|
||
|
self:GetParent():GetComponent(ComponentType.Direction):SetDir(mouseDir)
|
||
|
end
|
||
|
|
||
|
return _M.CreateComponent(o)
|
||
|
end
|
||
|
|
||
|
---@class InvincibleComponent:Component
|
||
|
---@field time number
|
||
|
|
||
|
---@return InvincibleComponent
|
||
|
---@param time number
|
||
|
function _M.CreateInvincibleComponent(time)
|
||
|
local o = { isActive = true, name = ComponentType.Invincible, cooldown = time, timeCountDown = time, parent = nil }
|
||
|
|
||
|
---@return boolean
|
||
|
---@param self InvincibleComponent
|
||
|
o.IsInvincibleState = function(self)
|
||
|
return self.timeCountDown > 0
|
||
|
end
|
||
|
|
||
|
---@param self InvincibleComponent
|
||
|
o.IntoInvincible = function(self)
|
||
|
if self.timeCountDown <= 0 then
|
||
|
self.timeCountDown = self.cooldown
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---@param self InvincibleComponent
|
||
|
o.Update = function(self)
|
||
|
self.timeCountDown = self.timeCountDown - hazel.Time.GetElapseTime()
|
||
|
if self.timeCountDown < 0 then
|
||
|
self.timeCountDown = 0
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return _M.CreateComponent(o)
|
||
|
end
|
||
|
|
||
|
---@class RolePropComponent:Component
|
||
|
---@field hp number
|
||
|
---@field maxHp number
|
||
|
---@field speed number
|
||
|
---@field damage number
|
||
|
|
||
|
---@return RolePropComponent
|
||
|
---@param hp number
|
||
|
---@param speed number
|
||
|
---@param damage number|nil
|
||
|
function _M.CreateRolePropComponent(hp, speed, damage)
|
||
|
local o = { isActive = true, name = ComponentType.RoleProp, maxHp = hp, hp = hp, speed = speed, damage = damage or 0, parent = nil }
|
||
|
---@param self RolePropComponent
|
||
|
o.IsDie = function(self)
|
||
|
return self.hp <= 0
|
||
|
end
|
||
|
o.Update = function(self)
|
||
|
if self.hp < 0 then
|
||
|
self.hp = 0
|
||
|
end
|
||
|
end
|
||
|
return _M.CreateComponent(o)
|
||
|
end
|
||
|
|
||
|
---@class HpShowComponent:Component
|
||
|
---@field size Point
|
||
|
|
||
|
---@return HpShowComponent
|
||
|
---@param size Point
|
||
|
function _M.CreateHpShowComponent(size)
|
||
|
local o = { isActive = true, name = ComponentType.HpShow, size = size, parent = nil }
|
||
|
|
||
|
---@param self HpShowComponent
|
||
|
o.Update = function(self)
|
||
|
---@type Point
|
||
|
local position = self:GetParent():GetComponent(ComponentType.Transform).position
|
||
|
local pos = hazel.CreatePos(position.x, position.y - self.size.y - 2)
|
||
|
|
||
|
---@type RolePropComponent
|
||
|
local roleProp = self:GetParent():GetComponent(ComponentType.RoleProp)
|
||
|
hazel.Renderer.SetDrawColor(1, 1, 1, 1)
|
||
|
hazel.Renderer.FillRect(pos.x, pos.y, size.x, size.y)
|
||
|
|
||
|
hazel.Renderer.SetDrawColor(0, 1, 0, 1)
|
||
|
hazel.Renderer.FillRect(pos.x, pos.y, size.x * roleProp.hp / roleProp.maxHp, size.y)
|
||
|
|
||
|
hazel.Renderer.SetDrawColor(0, 0, 0, 1)
|
||
|
hazel.Renderer.DrawRect(pos.x, pos.y, size.x, size.y)
|
||
|
end
|
||
|
return _M.CreateComponent(o)
|
||
|
end
|
||
|
|
||
|
|
||
|
---@class AIComponent:Component
|
||
|
|
||
|
---@return AIComponent
|
||
|
function _M.CreateAIComponent()
|
||
|
local o = { isActive = true, name = ComponentType.AI, parent = nil }
|
||
|
|
||
|
---@param self AIComponent
|
||
|
o.Update = function(self)
|
||
|
---@type DirectionComponent
|
||
|
local direction = self:GetParent():GetComponent(ComponentType.Direction)
|
||
|
|
||
|
local playerCenterX = content.PlayerEntity:GetComponent(ComponentType.Transform).position.x + constants.TileSize / 2
|
||
|
local playerCenterY = content.PlayerEntity:GetComponent(ComponentType.Transform).position.y + constants.TileSize / 2
|
||
|
local myselfCenterX = self:GetParent():GetComponent(ComponentType.Transform).position.x + constants.TileSize / 2
|
||
|
local myselfCenterY = self:GetParent():GetComponent(ComponentType.Transform).position.y + constants.TileSize / 2
|
||
|
|
||
|
---@type Point
|
||
|
local dir = vmath.Normalize(hazel.CreatePos(playerCenterX - myselfCenterX, playerCenterY - myselfCenterY))
|
||
|
direction:SetDir(dir)
|
||
|
|
||
|
---@type Point
|
||
|
local position = self:GetParent():GetComponent(ComponentType.Transform).position
|
||
|
local velocity = self:GetParent():GetComponent(ComponentType.RoleProp).speed
|
||
|
local elapseTime = hazel.Time.GetElapseTime()
|
||
|
position.x = position.x + velocity * dir.x * elapseTime
|
||
|
position.y = position.y + velocity * dir.y * elapseTime
|
||
|
end
|
||
|
|
||
|
return _M.CreateComponent(o)
|
||
|
end
|
||
|
|
||
|
---@class GunComponent:Component
|
||
|
---@field damage number
|
||
|
---@field speed Point
|
||
|
|
||
|
---@return GunComponent
|
||
|
---@param damage number
|
||
|
---@param velocity number
|
||
|
function _M.CreateGunComponent(damage, velocity)
|
||
|
local o = { isActive = true, name = ComponentType.Gun, damage = damage, timeCounter = 0, parent = nil }
|
||
|
---@param self GunComponent
|
||
|
o.Update = function(self)
|
||
|
if self.timeCounter < constants.GunInfo.cooldown then
|
||
|
self.timeCounter = self.timeCounter + hazel.Time.GetElapseTime()
|
||
|
end
|
||
|
if self.timeCounter >= constants.GunInfo.cooldown then
|
||
|
self.timeCounter = constants.GunInfo.cooldown
|
||
|
end
|
||
|
if hazel.GetMouseButtonState(hazel.MouseButton.Left) == hazel.InputState.Press and self.timeCounter >= constants.GunInfo.cooldown then
|
||
|
self.timeCounter = self.timeCounter - constants.GunInfo.cooldown
|
||
|
---@type Point
|
||
|
local position = self:GetParent():GetComponent(ComponentType.Transform).position
|
||
|
---@type Point
|
||
|
local mousePos = hazel.GetGameMousePos()
|
||
|
local playerCenterX = position.x + constants.TileSize / 2
|
||
|
local playerCenterY = position.y + constants.TileSize / 2
|
||
|
local nMouseDir = vmath.Normalize(hazel.CreatePos(mousePos.x - playerCenterX, mousePos.y - playerCenterY))
|
||
|
|
||
|
local bullet = _M.CreateBullet(hazel.CreatePos(playerCenterX - constants.TileSize / 2, playerCenterY - constants.TileSize / 2), self.damage, hazel.CreatePos(nMouseDir.x * velocity, nMouseDir.y * velocity))
|
||
|
table.insert(content.BulletList, bullet)
|
||
|
end
|
||
|
end
|
||
|
return _M.CreateComponent(o)
|
||
|
end
|
||
|
|
||
|
|
||
|
|
||
|
---@class BulletComponent:Component
|
||
|
---@field damage number
|
||
|
---@field speed Point
|
||
|
|
||
|
---@return BulletComponent
|
||
|
---@param damage number
|
||
|
---@param speed Point
|
||
|
function _M.CreateBulletComponent(damage, speed)
|
||
|
local o = { isActive = true, name = ComponentType.Bullet, damage = damage, speed = speed, parent = nil }
|
||
|
---@param self BulletComponent
|
||
|
o.Update = function(self)
|
||
|
local position = self:GetParent():GetComponent(ComponentType.Transform).position
|
||
|
local elapseTime = hazel.Time.GetElapseTime()
|
||
|
position.x = position.x + self.speed.x * elapseTime
|
||
|
position.y = position.y + self.speed.y * elapseTime
|
||
|
end
|
||
|
return _M.CreateComponent(o)
|
||
|
end
|
||
|
|
||
|
---@class ColliBoxComponent:Component
|
||
|
---@field rect Rect
|
||
|
|
||
|
---@return ColliBoxComponent
|
||
|
---@param rect Rect
|
||
|
function _M.CreateColliBoxComponent(rect)
|
||
|
local o = { isActive = true, name = ComponentType.ColliBox, rect = rect, parent = nil }
|
||
|
o.Update = function(self) end
|
||
|
return _M.CreateComponent(o)
|
||
|
end
|
||
|
|
||
|
|
||
|
---@return Entity
|
||
|
---@param pos Point
|
||
|
---@param damage number
|
||
|
---@param speed Point
|
||
|
function _M.CreateBullet(pos, damage, speed)
|
||
|
---@type Entity
|
||
|
local entity = _M.CreateEntity("Bullet")
|
||
|
entity:SetComponent(_M.CreateTransformComponent(pos, hazel.CreateSize(constants.TileSize, constants.TileSize)))
|
||
|
entity:SetComponent(_M.CreateBulletComponent(damage, speed))
|
||
|
entity:SetComponent(_M.CreateImageComponent(content.Tilesheet, 1, 4))
|
||
|
entity:SetComponent(_M.CreateColliBoxComponent(constants.BulletColliBox))
|
||
|
return entity
|
||
|
end
|
||
|
|
||
|
---@return Entity
|
||
|
---@param pos Point
|
||
|
function _M.CreatePlayer(pos)
|
||
|
local entity = _M.CreateEntity("player")
|
||
|
entity:SetComponent(_M.CreateTransformComponent(hazel.CreatePos(pos.x, pos.y), hazel.CreateSize(constants.TileSize, constants.TileSize), Flip.Vertical))
|
||
|
entity:SetComponent(_M.CreateImageComponent(content.Tilesheet, 0, 0))
|
||
|
entity:SetComponent(_M.CreateRolePropComponent(constants.PlayerInfo.hp, constants.PlayerInfo.velocity))
|
||
|
entity:SetComponent(_M.CreateDirectionComponent(0))
|
||
|
entity:SetComponent(_M.CreateColliBoxComponent(constants.RoleColliBox))
|
||
|
entity:SetComponent(_M.CreateInvincibleComponent(constants.Invincible))
|
||
|
return entity
|
||
|
end
|
||
|
|
||
|
---@return Entity
|
||
|
---@param pos Point
|
||
|
function _M.CreateMonster(pos)
|
||
|
local entity = _M.CreateEntity("monster")
|
||
|
entity:SetComponent(_M.CreateTransformComponent(hazel.CreatePos(pos.x, pos.y), hazel.CreateSize(constants.TileSize, constants.TileSize), Flip.Vertical))
|
||
|
entity:SetComponent(_M.CreateImageComponent(content.Tilesheet, 0, 5))
|
||
|
entity:SetComponent(_M.CreateRolePropComponent(constants.MonsterInfo.hp, constants.MonsterInfo.velocity, constants.MonsterInfo.damage))
|
||
|
entity:SetComponent(_M.CreateHpShowComponent(hazel.CreateSize(constants.MonsetHpBarInfo.width, constants.MonsetHpBarInfo.height)))
|
||
|
entity:SetComponent(_M.CreateDirectionComponent(5))
|
||
|
entity:SetComponent(_M.CreateColliBoxComponent(constants.RoleColliBox))
|
||
|
return entity
|
||
|
end
|
||
|
|
||
|
|
||
|
return _M
|