diff --git a/.gitignore b/.gitignore index d1930cf..ef79c2c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ testExecutor install TODO.txt .cache -compile_flags.txt \ No newline at end of file +compile_flags.txt +game/shader +game/*.dll +game/*.dll.a +game/*.exe \ No newline at end of file diff --git a/game/config.lua b/game/config.lua new file mode 100644 index 0000000..22c9eac --- /dev/null +++ b/game/config.lua @@ -0,0 +1,4 @@ +WindowWidth = 1024 +WindowHeight = 720 +Title = "InfiniteShoot - VisualGMQ For 1MGames v0.1.0" +EntryFile = "main.lua" \ No newline at end of file diff --git a/game/constants.lua b/game/constants.lua new file mode 100644 index 0000000..86c211e --- /dev/null +++ b/game/constants.lua @@ -0,0 +1,50 @@ +local _M = {} + +_M.TileSize = 32 +_M.PlayerInfo = { + velocity = 250, + hp = 100, +} +_M.MonsterInfo = { + velocity = 100, + hp = 50, + damage = 10, +} +_M.RoleColliBox = { + w = 14, + h = 32, + x = 9, + y = 0, +} +_M.Invincible = 1 +_M.BulletColliBox = { + x = 10, + y = 0, + w = 12, + h = 12, +} +_M.StonePutProbability = 0.1 +_M.PlayerHpBarInfo = { + width = _M.TileSize, + height = 5, +} +_M.MonsetHpBarInfo= { + width = _M.TileSize, + height = 5, +} +_M.GunInfo = { + cooldown = 0.1 +} +_M.BulletInfo = { + damage = 10, + velocity = 500, +} +_M.GunInfo = { + cooldown = 0.1 +} +_M.BulletInfo = { + damage = 10, + velocity = 500, +} + +return _M diff --git a/game/content.lua b/game/content.lua new file mode 100644 index 0000000..926aa2c --- /dev/null +++ b/game/content.lua @@ -0,0 +1,19 @@ +---@class Content +local _M = {} + +---@type Texture +_M.Texture = nil + +---@type TileSheet +_M.Tilesheet = nil + +---@type Entity +_M.PlayerEntity = nil + +---@type table +_M.BulletList = {} + +---@type table +_M.MonsterList = {} + +return _M \ No newline at end of file diff --git a/game/ecs.lua b/game/ecs.lua new file mode 100644 index 0000000..e13c37d --- /dev/null +++ b/game/ecs.lua @@ -0,0 +1,475 @@ +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 +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.CreateControllerComponent()) + entity:SetComponent(_M.CreateRolePropComponent(constants.PlayerInfo.hp, constants.PlayerInfo.velocity)) + entity:SetComponent(_M.CreateHpShowComponent(hazel.CreateSize(constants.PlayerHpBarInfo.width, constants.PlayerHpBarInfo.height))) + entity:SetComponent(_M.CreateGunComponent(constants.BulletInfo.damage, constants.BulletInfo.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)) + entity:SetComponent(_M.CreateAIComponent()) + return entity +end + + +return _M \ No newline at end of file diff --git a/game/hazel.lua b/game/hazel.lua new file mode 100644 index 0000000..bb97e6e --- /dev/null +++ b/game/hazel.lua @@ -0,0 +1,465 @@ +---@class libhazel +local libhazel = require "libhazel" + +---@class hazel +local _M = {} + +---@class Point +---@field x number +---@field y number + +---@class Rect +---@field x number +---@field y number +---@field w number +---@field h number + + +---@return Point +---@param x number +---@param y number +function _M.CreatePos(x, y) + return {x = x, y = y} +end + +---@return Point +---@param w number +---@param h number +function _M.CreateSize(w, h) + return {x = w, y = h} +end + + +---@return Rect +---@param x number +---@param y number +---@param w number +---@param h number +function _M.CreateRect(x, y, w, h) + return {x = x, y = y, w = w, h = h} +end + +---@class InputState +_M.InputState = { + Release = 0, + Press = 1, + Repeat = 2, +} + +---@class Key +_M.Key = { + Unkown = -1, + Space = 32, + Apostrophe = 39, + Comma = 44, + Minus = 45, + Period = 46, + Slash = 47, + K_0 = 48, + K_1 = 49, + K_2 = 50, + K_3 = 51, + K_4 = 52, + K_5 = 53, + K_6 = 54, + K_7 = 55, + K_8 = 56, + K_9 = 57, + Semicolon = 59, + Equal = 61, + A = 65, + B = 66, + C = 67, + D = 68, + E = 69, + F = 70, + G = 71, + H = 72, + I = 73, + J = 74, + K = 75, + L = 76, + M = 77, + N = 78, + O = 79, + P = 80, + Q = 81, + R = 82, + S = 83, + T = 84, + U = 85, + V = 86, + W = 87, + X = 88, + Y = 89, + Z = 90, + Left_bracket = 91, + Backslash = 92, + Right_bracket = 93, + Grave_accent = 96, + World_1 = 161, + World_2 = 162, + Escape = 256, + Enter = 257, + Tab = 258, + Backspace = 259, + Insert = 260, + Delete = 261, + Right = 262, + Left = 263, + Down = 264, + Up = 265, + Page_up = 266, + Page_down = 267, + Home = 268, + End = 269, + Caps_lock = 280, + Scroll_lock = 281, + Num_lock = 282, + Print_screen = 283, + Pause = 284, + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + F13 = 302, + F14 = 303, + F15 = 304, + F16 = 305, + F17 = 306, + F18 = 307, + F19 = 308, + F20 = 309, + F21 = 310, + F22 = 311, + F23 = 312, + F24 = 313, + F25 = 314, + KP_0 = 320, + KP_1 = 321, + KP_2 = 322, + KP_3 = 323, + KP_4 = 324, + KP_5 = 325, + KP_6 = 326, + KP_7 = 327, + KP_8 = 328, + KP_9 = 329, + KP_decimal = 330, + KP_divide = 331, + KP_multiply = 332, + KP_subtract = 333, + KP_add = 334, + KP_enter = 335, + KP_equal = 336, + LeftShift = 340, + LeftControl = 341, + LeftAlt = 342, + LeftSuper = 343, + RightShift = 344, + RightControl = 345, + RightAlt = 346, + RightSuper = 347, + Menu = 348, +} + +---@class MouseButton +_M.MouseButton = { + Left = 0, + Right = 1, + Middle = 2, +} + +---@return InputState +---@param key Key +function _M.GetKeyState(key) + return libhazel.GetKeyState(key) +end + +---@return boolean +---@param key number +function _M.IsKeyPressing(key) + return _M.GetKeyState(key) == _M.InputState.Press +end + +---@return boolean +---@param key number +function _M.IsKeyReleasing(key) + return _M.GetKeyState(key) == _M.InputState.Release +end + +---@return Point +function _M.GetMousePos() + local x, y = libhazel.GetMousePos() + return _M.CreatePos(x, y) +end + +---@return Point +function _M.GetGameMousePos() + local x, y = libhazel.GetMousePos() + local windowSize = _M.GetWindowSize() + local canvaSize = _M.GetCanvaSize() + return _M.CreatePos(x * canvaSize.x / windowSize.x, y * canvaSize.y / windowSize.y) +end + +function _M.HideCursor() + libhazel.HideCursor() +end + +function _M.ShowCursor() + libhazel.ShowCursor() +end + +---@return InputState +---@param btn MouseButton +function _M.GetMouseButtonState(btn) + return libhazel.GetMouseButtonState(btn) +end + +---@return number +function _M.GetTime() + return libhazel.GetTime() +end + +---@param filename string +function _M.SetWindowIcon(filename) + libhazel.WindowSetIcon(filename) +end + +---@return Point +function _M.GetCanvaSize() + local x, y = libhazel.GetCanvaSize() + return _M.CreatePos(x, y) +end + +---@return Point +function _M.GetWindowSize() + local x, y = libhazel.GetWindowSize() + return _M.CreatePos(x, y) +end + +---@class Texture +---@field w number +---@field h number + +---@return Texture +---@param filename string +function _M.LoadTexture(filename) + local texture = libhazel.LoadTexture(filename) + local w, h = libhazel.TextureGetSize(texture) + return {rawPointer = texture, w = w, h = h} +end + +---@param texture Texture +function _M.DestroyTexture(texture) + libhazel.DestroyTexture(texture.rawPointer) +end + +---@return Point +---@param texture Texture +function _M.GetTextureSize(texture) + local x, y = libhazel.TextureGetSize(texture) + return _M.CreatePos(x, y) +end + +function _M.SayHello() + libhazel.SayHello() +end + +---@class Flip +Flip = { + None = 0, + Vertical = 1, + Horizontal = 2, + Both = 3, +} + +_M.Flip = Flip + +---@class Renderer +local Renderer = { + ---@param r number + ---@param g number + ---@param b number + ---@param a number + SetClearColor = function(r, g, b, a) + libhazel.RenderSetClearColor(r, g, b, a or 1) + end, + + ---@param r number + ---@param g number + ---@param b number + ---@param a number + SetDrawColor = function(r, g, b, a) + libhazel.RenderSetDrawColor(r, g, b, a or 1) + end, + + ---@param x1 number + ---@param y1 number + ---@param x2 number + ---@param y2 number + DrawLine = function(x1, y1, x2, y2) + libhazel.RenderDrawLine(x1, y1, x2, y2) + end, + + ---@param p1 Point + ---@param p2 Point + DrawLineByPoints = function(p1, p2) + libhazel.RenderDrawLine(p1.x, p1.y, p2.x, p2.y) + end, + + ---@param x number + ---@param y number + ---@param w number + ---@param h number + DrawRect = function(x, y, w, h) + libhazel.RenderDrawRect(x, y, w, h) + end, + + ---@param rect Rect + DrawRectByRect = function(rect) + libhazel.RenderDrawRect(rect.x, rect.y, rect.w, rect.h) + end, + + ---@param x number + ---@param y number + ---@param w number + ---@param h number + FillRect = function(x, y, w, h) + libhazel.RenderFillRect(x, y, w, h) + end, + + ---@param rect Rect + FillRectByRect = function(rect) + libhazel.RenderFillRect(rect.x, rect.y, rect.w, rect.h) + end, + + ---@param texture Texture + ---@param srcrect Rect|nil + ---@param dstrect Rect|nil + ---@param flip number|nil + DrawTexture = function(texture, srcrect, dstrect, flip) + srcrect = srcrect or _M.CreateRect(0, 0, texture.w, texture.h) + local size = _M.GetCanvaSize(); + dstrect = dstrect or _M.CreateRect(0, 0, size.x, size.y) + libhazel.RenderDrawTexture(texture.rawPointer, + srcrect.x, srcrect.y, srcrect.w, srcrect.h, + dstrect.x, dstrect.y, dstrect.w, dstrect.h, + flip or Flip.None) + end, + + Clear = function() + libhazel.RenderClear() + end +} + +_M.Renderer = Renderer + +---@class Log +local Log = {} + +function Log.Loge(...) + print("[Lua Error]:", ...) +end + +function Log.Logw(...) + print("[Lua Warn]:", ...) +end + +function Log.Logi(...) + print("[Lua Info]:", ...) +end + +_M.Log = Log + + +---@class TextureStorage +local TextureStorage = { + storage = {} +} + +---@param filename string +---@param name string +---@return Texture +function TextureStorage.LoadTexture(filename, name) + local oldTexture = TextureStorage.storage[name] + if oldTexture then + _M.Log.Logw(string.format("%s already exists, delete it", name)) + _M.DestroyTexture(oldTexture) + end + local texture = _M.LoadTexture(filename) + TextureStorage.storage[name] = texture + return texture +end + +function TextureStorage.ClearAll() + for _, v in pairs(TextureStorage.storage) do + if v then + _M.DestroyTexture(v) + end + end +end + +---@return Texture +---@param name string +function TextureStorage.Get(name) + return TextureStorage.storage[name] +end + +_M.TextureStorage = TextureStorage + +---@class TileSheet +---@field column number +---@field row number +---@field texture Texture +---@field tileWidth number +---@field tileHeight number +local TileSheet = {} + +---@return TileSheet +---@param texture Texture +---@param column number +---@param row number +function _M.CreateTileSheet(texture, column, row) + local o = {texture = texture, column = column, row = row, tileWidth = texture.w / column, tileHeight = texture.h / row} + setmetatable(o, {__index = TileSheet}) + return o +end + +---@param self TileSheet +---@param column number +---@param row number +---@param dstrect Rect +---@param flip number +function TileSheet.Draw(self, column, row, dstrect, flip) + local srcrect = _M.CreateRect(column * self.tileWidth, row * self.tileHeight, self.tileWidth, self.tileHeight) + _M.Renderer.DrawTexture(self.texture, srcrect, dstrect, flip) +end + +_M.TileSheet = TileSheet + + +local Time = { curTime = 0, elapseTime = 0 } + +function Time.RecordElapseTime() + local oldTime = Time.curTime + Time.curTime = libhazel.GetTime() + Time.elapseTime = Time.curTime - oldTime +end + +---@return number the passed seconds between two frames +function Time.GetElapseTime() + return Time.elapseTime +end + +_M.Time = Time + +return _M diff --git a/game/main.lua b/game/main.lua new file mode 100644 index 0000000..fd419eb --- /dev/null +++ b/game/main.lua @@ -0,0 +1,174 @@ +local hazel = require "hazel" +local ECS = require "ecs" +local constants = require "constants" +local math = require "math" +local content = require "content" +local vmath = require "vmath" + +local function drawCurosr() + hazel.Renderer.SetDrawColor(1, 0, 0, 1) + local pos = hazel.GetGameMousePos() + local halfW = 10 + local halfH = 10 + hazel.Renderer.DrawRect(pos.x - halfW, pos.y - halfH, halfW * 2, halfH * 2) + hazel.Renderer.DrawLine(pos.x - halfW + 5, pos.y, pos.x + halfW - 5, pos.y) + hazel.Renderer.DrawLine(pos.x, pos.y - halfH + 5, pos.x, pos.y + halfH - 5) +end + +local FloorMap = {} + +local function generateFloors() + local xNum = math.ceil(hazel.GetCanvaSize().x / constants.TileSize) + local yNum = math.ceil(hazel.GetCanvaSize().y / constants.TileSize) + for x = 0, xNum do + for y = 0, yNum do + FloorMap[y + x * yNum] = math.random() <= constants.StonePutProbability + end + end +end + +local function drawFloors() + local xNum = math.ceil(hazel.GetCanvaSize().x / constants.TileSize) + local yNum = math.ceil(hazel.GetCanvaSize().y / constants.TileSize) + local tileIndex = {row = 9, col = 0} + for x = 0, xNum do + for y = 0, yNum do + if FloorMap[y + x * yNum] then + tileIndex.col = 1 + else + tileIndex.col = 0 + end + content.Tilesheet:Draw(tileIndex.col, tileIndex.row, + hazel.CreateRect(x * constants.TileSize, y * constants.TileSize, + constants.TileSize, constants.TileSize), + hazel.Flip.None) + end + end +end + +local function updateBullet() + for k, _ in pairs(content.BulletList) do + ---@type Entity + local bullet = content.BulletList[k] + bullet:Update() + ---@type Point + local position = bullet:GetComponent(ECS.ComponentType.Transform).position + if position.x + constants.TileSize <= 0 or + position.x >= hazel.GetCanvaSize().x or + position.y + constants.TileSize <= 0 or + position.y >= hazel.GetCanvaSize().y then + content.BulletList[k] = nil + end + end +end + +local function updateMonster() + ---@param v Entity + for _, v in pairs(content.MonsterList) do + v:Update() + end +end + +local function collisionDeal() + ---@type Rect + local playerBox = content.PlayerEntity:GetComponent(ECS.ComponentType.ColliBox).rect + ---@type Point + local playerPos = content.PlayerEntity:GetComponent(ECS.ComponentType.Transform).position + ---@type Rect + local playerColliBox = hazel.CreateRect(playerPos.x + playerBox.x, playerPos.y + playerBox.y, playerBox.w, playerBox.h) + ---@type m Entity + for km, _ in pairs(content.MonsterList) do + ---@type Entity + local monster = content.MonsterList[km] + ---@type Point + local monsterPos = monster:GetComponent(ECS.ComponentType.Transform).position + ---@type ColliBoxComponent + local monsterBoxComponent = monster:GetComponent(ECS.ComponentType.ColliBox) + if monsterBoxComponent then + ---@type Rect + local monsterBox = monsterBoxComponent.rect + ---@type Rect + local monsterColliBox = hazel.CreateRect(monsterPos.x + monsterBox.x, monsterPos.y + monsterBox.y, + monsterBox.w, monsterBox.h) + ---@type RolePropComponent + local monsterRoleProp = monster:GetComponent(ECS.ComponentType.RoleProp) + if vmath.IsRectIntersect(playerColliBox, monsterColliBox) then + ---@type InvincibleComponent + local playerInvincible = content.PlayerEntity:GetComponent(ECS.ComponentType.Invincible) + if not playerInvincible:IsInvincibleState() then + ---@type RolePropComponent + local playerRoleProp = content.PlayerEntity:GetComponent(ECS.ComponentType.RoleProp) + playerRoleProp.hp = playerRoleProp.hp - monsterRoleProp.damage + if playerRoleProp:IsDie() then + content.PlayerEntity:RemoveComponent(ECS.ComponentType.Controller) + content.PlayerEntity:RemoveComponent(ECS.ComponentType.Gun) + content.PlayerEntity:RemoveComponent(ECS.ComponentType.Direction) + content.PlayerEntity:RemoveComponent(ECS.ComponentType.HpShow) + ---@type ImageComponent + local image = content.PlayerEntity:GetComponent(ECS.ComponentType.Image) + image.row = 4 + image.col = 0 + else + playerInvincible:IntoInvincible() + end + end + end + + for kb, _ in pairs(content.BulletList) do + ---@type Entity + local bullet = content.BulletList[kb] + ---@type Point + local bulletPos = bullet:GetComponent(ECS.ComponentType.Transform).position + ---@type Rect + local bulletBox = bullet:GetComponent(ECS.ComponentType.ColliBox).rect + ---@type Rect + local bulletColliBox = hazel.CreateRect(bulletPos.x + bulletBox.x, bulletPos.y + bulletBox.y, bulletBox.w, bulletBox.h) + if vmath.IsRectIntersect(bulletColliBox, monsterColliBox) then + content.BulletList[kb] = nil + local damage = bullet:GetComponent(ECS.ComponentType.Bullet).damage + monsterRoleProp.hp = monsterRoleProp.hp - damage + if monsterRoleProp:IsDie() then + monster:RemoveComponent(ECS.ComponentType.Direction) + monster:RemoveComponent(ECS.ComponentType.AI) + monster:RemoveComponent(ECS.ComponentType.HpShow) + monster:RemoveComponent(ECS.ComponentType.ColliBox) + ---@type ImageComponent + local image = monster:GetComponent(ECS.ComponentType.Image) + image.row = 4 + image.col = 2 + end + end + end + end + end +end + +function GameStart() + hazel.SetWindowIcon("resources/icon.png") + content.Texture = hazel.LoadTexture("resources/tilesheet.png") + content.Tilesheet = hazel.CreateTileSheet(content.Texture, 3, 10) + content.PlayerEntity = ECS.CreatePlayer(hazel.CreatePos(constants.TileSize * 10, constants.TileSize * 10)) + table.insert(content.MonsterList, ECS.CreateMonster(hazel.CreatePos(500, 500))) + + hazel.HideCursor() + generateFloors() +end + +function GameLoop() + hazel.Time.RecordElapseTime() + + hazel.Renderer.SetClearColor(0, 0, 0, 1) + hazel.Renderer.Clear() + + drawFloors() + updateMonster() + content.PlayerEntity:Update() + updateBullet() + collisionDeal() + drawCurosr() +end + +function GameQuit() + hazel.ShowCursor() + hazel.DestroyTexture(content.Texture) +end diff --git a/game/resources/icon.png b/game/resources/icon.png new file mode 100644 index 0000000..0536da1 Binary files /dev/null and b/game/resources/icon.png differ diff --git a/game/resources/tilesheet.png b/game/resources/tilesheet.png new file mode 100644 index 0000000..7997510 Binary files /dev/null and b/game/resources/tilesheet.png differ diff --git a/game/vmath.lua b/game/vmath.lua new file mode 100644 index 0000000..d4605c5 --- /dev/null +++ b/game/vmath.lua @@ -0,0 +1,28 @@ +local math = require "math" +local hazel = require "hazel" +local _M = {} + +---@return number +---@param p Point +function _M.Len(p) + return math.sqrt(p.x * p.x + p.y * p.y) +end + +---@return Point +---@param p +function _M.Normalize(p) + local l = _M.Len(p) + return hazel.CreatePos(p.x / l, p.y / l) +end + +---@return boolean +---@param r1 Rect +---@param r2 Rect +function _M.IsRectIntersect(r1, r2) + return not (r1.x + r1.w < r2.x or + r2.x + r2.w < r1.x or + r2.y + r2.h < r1.y or + r1.y + r1.h < r2.y) +end + +return _M \ No newline at end of file