From b829b49acb4eefe90dff67b4be467e0203d6700a Mon Sep 17 00:00:00 2001 From: VisualGMQ <2142587070@qq.com> Date: Sun, 31 Jul 2022 22:33:30 +0800 Subject: [PATCH] add game files --- .gitignore | 6 +- game/config.lua | 4 + game/constants.lua | 50 ++++ game/content.lua | 19 ++ game/ecs.lua | 475 +++++++++++++++++++++++++++++++++++ game/hazel.lua | 465 ++++++++++++++++++++++++++++++++++ game/main.lua | 174 +++++++++++++ game/resources/icon.png | Bin 0 -> 399 bytes game/resources/tilesheet.png | Bin 0 -> 5571 bytes game/vmath.lua | 28 +++ 10 files changed, 1220 insertions(+), 1 deletion(-) create mode 100644 game/config.lua create mode 100644 game/constants.lua create mode 100644 game/content.lua create mode 100644 game/ecs.lua create mode 100644 game/hazel.lua create mode 100644 game/main.lua create mode 100644 game/resources/icon.png create mode 100644 game/resources/tilesheet.png create mode 100644 game/vmath.lua 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 0000000000000000000000000000000000000000..0536da1a7c4c91d90467ff55fb6d6f12043d1cae GIT binary patch literal 399 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 q^)EOHs&loTLPSJ!a2r*Sa%5Wu*~)|8ZD#(J1