diff --git a/.gitignore b/.gitignore index f2bfc24..079b991 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,7 @@ install TODO.txt .cache compile_flags.txt -game/shader +*.zip game/*.dll -game/*.dll.a game/*.exe -*.zip \ No newline at end of file +game/shader diff --git a/CMakeLists.txt b/CMakeLists.txt index ba97bfd..c1b8e18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ include(cmake/FindSTB.cmake) # hazel-core aux_source_directory(src HAZEL_SRC) add_library(${HAZEL_CORE_NAME} SHARED ${HAZEL_SRC}) -target_link_libraries(${HAZEL_CORE_NAME} PUBLIC glad glfw stb_image) +target_link_libraries(${HAZEL_CORE_NAME} PUBLIC glad glfw stb_image "winmm") target_include_directories(${HAZEL_CORE_NAME} PUBLIC .) target_compile_features(${HAZEL_CORE_NAME} PUBLIC c_std_11) target_precompile_headers(${HAZEL_CORE_NAME} PUBLIC hazel/pch.h) diff --git a/binding/lua/bridge.c b/binding/lua/bridge.c index 39700cb..9c1006c 100644 --- a/binding/lua/bridge.c +++ b/binding/lua/bridge.c @@ -5,6 +5,7 @@ #include "hazel/hazel.h" #include "hazel/renderer.h" +#include "hazel/sound.h" static int LuaBridge_RenderSetClearColor(lua_State* L) { float r = luaL_checknumber(L, 1); @@ -145,16 +146,29 @@ static int LuaBridge_GetWindowSize(lua_State* L) { return 2; } -static int LuaBridge_HideCursor() { +static int LuaBridge_HideCursor(lua_State* L) { Hazel_HideCursor(); return 0; } -static int LuaBridge_ShowCursor() { +static int LuaBridge_ShowCursor(lua_State* L) { Hazel_ShowCursor(); return 0; } +static int LuaBridge_LoadSound(lua_State* L) { + const char* filename = luaL_checkstring(L, 1); + const char* soundName = luaL_checkstring(L, 2); + Hazel_LoadSound(filename, soundName); + return 0; +} + +static int LuaBridge_PlaySound(lua_State* L) { + const char* soundName = luaL_checkstring(L, 1); + Hazel_PlaySound(soundName); + return 0; +} + static const struct luaL_Reg libhazel[] = { {"RenderSetClearColor", LuaBridge_RenderSetClearColor}, {"RenderSetDrawColor", LuaBridge_RenderSetDrawColor}, @@ -176,6 +190,8 @@ static const struct luaL_Reg libhazel[] = { {"HideCursor", LuaBridge_HideCursor}, {"ShowCursor", LuaBridge_ShowCursor}, {"SayHello", LuaBridge_SayHello}, + {"LoadSound", LuaBridge_LoadSound}, + {"PlaySound", LuaBridge_PlaySound}, {NULL, NULL}, }; diff --git a/binding/lua/hazel_bind.lua b/binding/lua/hazel_bind.lua index bb97e6e..ac26b1f 100644 --- a/binding/lua/hazel_bind.lua +++ b/binding/lua/hazel_bind.lua @@ -462,4 +462,19 @@ end _M.Time = Time +local Sound = {} + +---@param filename string +---@param name string +function Sound.LoadSound(filename, name) + libhazel.Load(filename, name); +end + +---@param name string +function Sound.Play(name) + libhazel.PlaySound( name); +end + +_M.Sound = Sound + return _M diff --git a/game/TODO.md b/game/TODO.md index da2c85b..a3422ac 100644 --- a/game/TODO.md +++ b/game/TODO.md @@ -1,14 +1,14 @@ [important] [x] 加上1MGames标志 -[ ] 角色走路动画 -[ ] 角色受伤特效 +[x] 增加音效 [x] 角色无敌时闪烁 -[ ] 新增多个枪类型 -[ ] 右上角显示分数 -[ ] 玩家积分榜 +[x] 新增多个枪类型 +[x] 右上角显示分数 + +[FIXME] +[ ] Component的Update顺序是随机的 [optional] [ ] 怪物从土里爬出来(新增怪物类型) -[ ] 掉落宝物(掉落子弹,加分道具) -[ ] 多把武器切换 \ No newline at end of file +[ ] 多把武器切换 diff --git a/game/animation.lua b/game/animation.lua index 51864aa..d157608 100644 --- a/game/animation.lua +++ b/game/animation.lua @@ -8,7 +8,7 @@ local _M = {} ---@field col number ---@field time number ----@param tilesheet Texture +---@param tilesheet TileSheet ---@param frames table ---@param onEndCallback function function _M.CreateAnimation(tilesheet, frames, onEndCallback) @@ -42,6 +42,8 @@ function _M.GetCurFrame(self) end end +---@return TileSheet +---@param self Animation function _M.GetTilesheet(self) return self.tilesheet end diff --git a/game/constants.lua b/game/constants.lua index 050257c..11b466e 100644 --- a/game/constants.lua +++ b/game/constants.lua @@ -2,6 +2,13 @@ local _M = {} _M.ShowLicenseTime = 2.5 +_M.SoundName = { + Shoot = "SHOOT", + PlayerHurt = "PLAYER_HURT", + MonsterHurt = "MONSTER_HURT", + GameOver = "GAMEOVER", +} + _M.TileSize = 32 _M.PlayerInfo = { velocity = 250, @@ -18,6 +25,12 @@ _M.RoleColliBox = { x = 9, y = 0, } +_M.SupplyColliBox = { + w = 32, + h = 32, + x = 9, + y = 0, +} _M.MonsterBirthInterval = 1 _M.MonsterBirthInitNum = 1 _M.Invincible = 1 @@ -39,16 +52,55 @@ _M.MonsetHpBarInfo= { _M.GunInfo = { cooldown = 0.1 } -_M.BulletInfo = { - damage = 10, - velocity = 500, -} -_M.GunInfo = { - cooldown = 0.1 + +_M.BulletType = { + Normal = 1, + Ice = 2, + Fire = 3, } + _M.BulletInfo = { - damage = 10, - velocity = 500, + [_M.BulletType.Normal] = { + damage = 10, + velocity = 500, + cooldown = 0.1, + initNum = -1, + }, + [_M.BulletType.Ice] = { + damage = 5, + velocity = 400, + cooldown = 0.1, + initNum = 100, + }, + [_M.BulletType.Fire] = { + damage = 7, + velocity = 600, + cooldown = 0.1, + fireDamage = 1, + initNum = 100, + }, +} + +_M.BulletEffectTime = { + IceTime = 2, + FireTime = 2, +} +_M.RoleState = { + Normal = 1, + Ice = 2, + Fire = 3, +} +_M.SupplyFalldownKillNum = 120 + +_M.SupplyType = { + IceGun = 1, + FireGun = 2, + HpRecover = 3, +} +_M.SupplyItem = { + [_M.SupplyType.IceGun] = { num = 100 }, + [_M.SupplyType.FireGun] = { num = 100 }, + [_M.SupplyType.HpRecover] = { recover = 50}, } return _M diff --git a/game/content.lua b/game/content.lua index c8cd263..9ca6b76 100644 --- a/game/content.lua +++ b/game/content.lua @@ -13,9 +13,15 @@ _M.StartHintTexture = nil ---@type Texture _M.LicensTexture = nil +---@type Texture +_M.NumberTexture = nil + ---@type TileSheet _M.Tilesheet = nil +---@type TileSheet +_M.NumberTilesheet = nil + ---@type Entity _M.PlayerEntity = nil @@ -57,4 +63,12 @@ _M.Animations = { EnemyWalkLeft = nil, } +---@type number +_M.KillNum = 0 +---@type number +_M.Score = 0 + +---@type table +_M.SupplyList = {} + return _M \ No newline at end of file diff --git a/game/ecs.lua b/game/ecs.lua index df5e811..3ee6adb 100644 --- a/game/ecs.lua +++ b/game/ecs.lua @@ -4,6 +4,8 @@ local math = require "math" local vmath = require "vmath" local content = require "content" local timer = require "timer" +local animation = require "animation" +local helpfuncs = require "helpfunc" ---@class ECS local _M = {} @@ -29,7 +31,7 @@ function Entity.SetComponent(self, component) end ---@return Component|nil ----@param type string +---@param type number function Entity.GetComponent(self, type) if not type then return nil @@ -102,17 +104,20 @@ 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", + Transform = 1, + Controller = 2, + RoleProp = 3, + HpShow = 4, + Gun = 5, + Bullet = 6, + Direction = 7, + AI = 8, + ColliBox = 9, + Invincible = 10, + Animator = 11, + State = 12, + Supply = 13, + Image = 14, } _M.ComponentType = ComponentType @@ -215,6 +220,8 @@ function _M.CreateControllerComponent() ---@type TransformComponent local transform = self:GetParent():GetComponent(ComponentType.Transform) + ---@type AnimatorComponent + local animator = self:GetParent():GetComponent(ComponentType.Animator) local elapseTime = hazel.Time.GetElapseTime() if hazel.IsKeyPressing(hazel.Key.A) then transform.position.x = transform.position.x - speed * elapseTime @@ -379,6 +386,10 @@ function _M.CreateAIComponent() ---@param self AIComponent o.Update = function(self) + local state = self:GetParent():GetComponent(ComponentType.State):GetState() + if state == constants.RoleState.Ice then + return + end ---@type DirectionComponent local direction = self:GetParent():GetComponent(ComponentType.Direction) @@ -407,31 +418,50 @@ end ---@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, canShoot = true, velocity = velocity, parent = nil } - o.cdTimer = timer.CreateTimer(constants.GunInfo.cooldown, -1, function() +---@param type number +---@param bulletNum number|nil +function _M.CreateGunComponent(type, bulletNum) + local o = { isActive = true, name = ComponentType.Gun, type = type, canShoot = true, parent = nil } + o.bulletNum = constants.BulletInfo[type or constants.BulletType.Normal].initNum + o.cdTimer = timer.CreateTimer(constants.BulletInfo[type].cooldown, -1, function() o.canShoot = true end) + o.SetType = function(self, type) + self.type = type or constants.BulletType.Normal + self.bulletNum = constants.BulletInfo[self.type].initNum + end + + o.GetBulletNum = function(self) + return self.bulletNum + end + ---@param self GunComponent ---@param dir Point o.Fire = function(self, dir) - if not self.canShoot then + if not self.canShoot or self.bulletNum == 0 then return end + hazel.Sound.Play(constants.SoundName.Shoot) ---@type Point local position = self:GetParent():GetComponent(ComponentType.Transform).position local playerCenterX = position.x + constants.TileSize / 2 local playerCenterY = position.y + constants.TileSize / 2 local ndir = vmath.Normalize(dir) + local velocity = constants.BulletInfo[type].velocity local bullet = _M.CreateBullet(hazel.CreatePos(playerCenterX - constants.TileSize / 2, playerCenterY - constants.TileSize / 2), - self.damage, - hazel.CreatePos(ndir.x * self.velocity, ndir.y * self.velocity)) + constants.BulletInfo[type].damage, + hazel.CreatePos(ndir.x * velocity, ndir.y * velocity), + self.type) table.insert(content.BulletList, bullet) self.canShoot = false + hazel.Sound.Play(constants.SoundName.Shoot) + self.bulletNum = self.bulletNum - 1 + + if self.bulletNum == 0 then + self:SetType(constants.BulletType.Normal) + end end ---@param self GunComponent @@ -450,9 +480,14 @@ end ---@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 type number +function _M.CreateBulletComponent(damage, speed, type) + local o = { isActive = true, name = ComponentType.Bullet, damage = damage, type = type, speed = speed, parent = nil } ---@param self BulletComponent + o.GetType = function(self) + return self.type + end + o.Update = function(self) local position = self:GetParent():GetComponent(ComponentType.Transform).position local elapseTime = hazel.Time.GetElapseTime() @@ -474,16 +509,150 @@ function _M.CreateColliBoxComponent(rect) end +---@class SupplyComponent:Component +---@field rect Rect + +---@return SupplyComponent +---@param type number +function _M.CreateSupplyComponent(type) + local o = { isActive = true, name = ComponentType.Supply, type = type, parent = nil } + o.Update = function(self) end + return _M.CreateComponent(o) +end + + +---@class StateComponent:Component +---@field rect Rect + +---@return StateComponent +---@param state number|nil +function _M.CreateStateComponent(state) + local o = { isActive = true, name = ComponentType.State, state = state or constants.RoleState.Normal, parent = nil } + ---@type Animation + o.fireAnimation = animation.CreateAnimation(content.Tilesheet, { + {row = 10, col = 2, time = 0.2}, + {row = 11, col = 0, time = 0.2}, + }, function() + o.fireAnimation:Rewind() + o.fireAnimation:Play() + end) + o.GetState = function(self) + return self.state + end + o.IntoIce = function(self) + self.state = constants.RoleState.Ice + self.cdTimer = timer.CreateTimer(constants.BulletEffectTime.IceTime, 1, function() + if self.state == constants.RoleState.Ice then + self.state = constants.RoleState.Normal + end + end) + end + o.IntoFire = function(self) + self.state = constants.RoleState.Fire + self.fireAnimation:Play() + self.cdTimer = timer.CreateTimer(constants.BulletEffectTime.FireTime, 1, function() + self.fireAnimation:Stop() + if self.state == constants.RoleState.Fire then + self.state = constants.RoleState.Normal + end + end) + end + ---@param self StateComponent + o.Update = function(self) + self.fireAnimation:Update() + if self.fireAnimation:IsPlaying() then + local frame = self.fireAnimation:GetCurFrame() + ---@type Point + local position = self:GetParent():GetComponent(ComponentType.Transform).position + self.fireAnimation:GetTilesheet():Draw(frame.col, frame.row, hazel.CreateRect(position.x, position.y, constants.TileSize, constants.TileSize)) + end + if self.state == constants.RoleState.Ice then + local position = self:GetParent():GetComponent(ComponentType.Transform).position + content.Tilesheet:Draw(2, 9, hazel.CreateRect(position.x, position.y, constants.TileSize, constants.TileSize)) + elseif self.state == constants.RoleState.Fire then + ---@type RolePropComponent + local roleProp = self:GetParent():GetComponent(ComponentType.RoleProp) + local oldHp = roleProp.hp + roleProp.hp = roleProp.hp - constants.BulletInfo[constants.BulletType.Fire].fireDamage + if oldHp > 0 and roleProp.hp <= 0 then + content.Score = content.Score + 1 + helpfuncs.IncKillNum() + end + end + if self.cdTimer then + self.cdTimer:Update() + end + end + return _M.CreateComponent(o) +end + +---@class AnimatorComponent:Component +---@field Play function +---@field Stop function +---@field Pause function +---@field Rewind function +---@field SetAnimation function +---@field GetAnimation function + +---@param ani Animation +function _M.CreateAnimatorComponent(ani) + local o = { isActive = true, name = ComponentType.Animator, ani = ani, parent = nil } + + o.Play = function(self) self.ani:Play() end + o.Pause = function(self) self.ani:Pause() end + o.Rewind = function(self) self.ani:Rewind() end + o.Stop = function(self) + self.ani:Stop() + self.ani:Rewind() + end + o.SetAnimation = function(self, ani) + if self.ani then + self.ani:Stop() + self.ani:Rewind() + self.ani = ani + end + end + o.GetAnimation = function(self) return self.ani end + + ---@param self AnimatorComponent + o.Update = function(self) + if not self.ani then + return + end + self.ani:Update() + ---@type ImageComponent + local image = self:GetParent():GetComponent(ComponentType.Image) + if image then + local frame = self.ani:GetCurFrame() + if frame then + image.row = frame.row + image.col = frame.col + image.tilesheet = self.ani:GetTilesheet() + end + end + end + return _M.CreateComponent(o) +end + + ---@return Entity ---@param pos Point ---@param damage number ---@param speed Point -function _M.CreateBullet(pos, damage, speed) +---@param type number|nil +function _M.CreateBullet(pos, damage, speed, type) + type = type or constants.BulletType.Normal ---@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.CreateBulletComponent(damage, speed, type)) + if type == constants.BulletType.Normal then + entity:SetComponent(_M.CreateImageComponent(content.Tilesheet, 1, 4)) + elseif type == constants.BulletType.Ice then + entity:SetComponent(_M.CreateImageComponent(content.Tilesheet, 0, 10)) + elseif type == constants.BulletType.Fire then + entity:SetComponent(_M.CreateImageComponent(content.Tilesheet, 1, 10)) + end entity:SetComponent(_M.CreateColliBoxComponent(constants.BulletColliBox)) return entity end @@ -496,9 +665,10 @@ function _M.CreatePlayer(pos) 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.CreateGunComponent(constants.BulletInfo.damage, constants.BulletInfo.velocity)) + entity:SetComponent(_M.CreateGunComponent(constants.BulletType.Fire)) entity:SetComponent(_M.CreateColliBoxComponent(constants.RoleColliBox)) entity:SetComponent(_M.CreateInvincibleComponent(constants.Invincible)) + entity:SetComponent(_M.CreateStateComponent()) return entity end @@ -512,6 +682,20 @@ function _M.CreateMonster(pos) 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.CreateStateComponent()) + return entity +end + + +---@return Entity +---@param type number +---@param pos Point +function _M.CreateSupply(type, pos) + local entity = _M.CreateEntity("supply") + entity:SetComponent(_M.CreateTransformComponent(pos, hazel.CreateSize(constants.TileSize, constants.TileSize))) + entity:SetComponent(_M.CreateImageComponent(content.Tilesheet, 1, 11)) + entity:SetComponent(_M.CreateColliBoxComponent(constants.SupplyColliBox)) + entity:SetComponent(_M.CreateSupplyComponent(type)) return entity end diff --git a/game/hazel.lua b/game/hazel.lua index bb97e6e..a199fc2 100644 --- a/game/hazel.lua +++ b/game/hazel.lua @@ -462,4 +462,21 @@ end _M.Time = Time + +local Sound = {} + +---@param filename string +---@param name string +function Sound.Load(filename, name) + libhazel.LoadSound(filename, name); +end + +---@param name string +function Sound.Play(name) + libhazel.PlaySound( name); +end + +_M.Sound = Sound + + return _M diff --git a/game/helpfunc.lua b/game/helpfunc.lua new file mode 100644 index 0000000..7d0a2d5 --- /dev/null +++ b/game/helpfunc.lua @@ -0,0 +1,31 @@ +---@class helpfunc + +---@type helpfunc +_M = {} + +local constants = require "constants" +---@type Content +local content = require "content" +---@type hazel +local hazel = require "hazel" + +_M.GenerateSupply = function() + local type = math.random(1, 3) + local canvaSize = hazel.GetCanvaSize() + ---@type Entity + ---@type ECS + local ECS = require "ecs" + local supply = ECS.CreateSupply(type, + hazel.CreatePos(math.random(0, canvaSize.x - constants.TileSize), + math.random(0, canvaSize.y - constants.TileSize))) + table.insert(content.SupplyList, supply) +end + +_M.IncKillNum = function() + content.KillNum = content.KillNum + 1 + if content.KillNum % constants.SupplyFalldownKillNum == 0 then + _M.GenerateSupply() + end +end + +return _M \ No newline at end of file diff --git a/game/main.lua b/game/main.lua index 353a578..5af8d48 100644 --- a/game/main.lua +++ b/game/main.lua @@ -5,6 +5,7 @@ local content = require "content" local vmath = require "vmath" local timer = require "timer" local animation = require "animation" +local helpfunc = require "helpfunc" local function drawCurosr() hazel.Renderer.SetDrawColor(1, 0, 0, 1) @@ -70,6 +71,13 @@ local function updateMonster() end end +local function updateSupply() + ---@param v Entity + for _, v in pairs(content.SupplyList) do + v:Update() + end +end + local function collisionDeal() ---@type Rect local playerBox = content.PlayerEntity:GetComponent(ECS.ComponentType.ColliBox).rect @@ -96,13 +104,18 @@ local function collisionDeal() if vmath.IsRectIntersect(playerColliBox, monsterColliBox) then ---@type InvincibleComponent local playerInvincible = content.PlayerEntity:GetComponent(ECS.ComponentType.Invincible) - if not playerInvincible:IsInvincibleState() then + if playerInvincible and not playerInvincible:IsInvincibleState() then ---@type RolePropComponent local playerRoleProp = content.PlayerEntity:GetComponent(ECS.ComponentType.RoleProp) + local oldHp = playerRoleProp.hp playerRoleProp.hp = playerRoleProp.hp - monsterRoleProp.damage + hazel.Sound.Play(constants.SoundName.PlayerHurt) if playerRoleProp:IsDie() then + content.PlayerEntity:RemoveComponent(ECS.ComponentType.Invincible) + if oldHp > 0 then + hazel.Sound.Play(constants.SoundName.GameOver) + end 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 @@ -127,17 +140,53 @@ local function collisionDeal() 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 + local oldHp = monsterRoleProp.hp + local bulletType = bullet:GetComponent(ECS.ComponentType.Bullet):GetType() + if bulletType == constants.BulletType.Ice then + monster:GetComponent(ECS.ComponentType.State):IntoIce() + elseif bulletType == constants.BulletType.Fire then + monster:GetComponent(ECS.ComponentType.State):IntoFire() end + monsterRoleProp.hp = monsterRoleProp.hp - damage + + hazel.Sound.Play(constants.SoundName.MonsterHurt) + if monsterRoleProp:IsDie() then + if oldHp > 0 then + content.Score = content.Score + 1 + helpfunc.IncKillNum() + end + end + end + end + end + + ---@param v Entity + for i, v in pairs(content.SupplyList) do + ---@type Point + local supplyPos = v:GetComponent(ECS.ComponentType.Transform).position + ---@type Rect + local supplyBox = v:GetComponent(ECS.ComponentType.ColliBox).rect + ---@type Rect + local supplyColliBox = hazel.CreateRect(supplyPos.x + supplyBox.x, supplyPos.y + supplyBox.y, supplyBox.w, supplyBox.h) + if vmath.IsRectIntersect(supplyColliBox, playerColliBox) then + content.SupplyList[i] = nil + ---@type GunComponent + local gun = content.PlayerEntity:GetComponent(ECS.ComponentType.Gun) + local type = v:GetComponent(ECS.ComponentType.Supply).type + print(type) + if type == constants.SupplyType.HpRecover then + ---@type RolePropComponent + local roleProp = content.PlayerEntity:GetComponent(ECS.ComponentType.RoleProp) + roleProp.hp = roleProp.hp + constants.SupplyItem[type].recover + if roleProp.hp > constants.PlayerInfo.hp then + roleProp.hp = constants.PlayerInfo.hp + end + elseif type == constants.SupplyType.IceGun then + gun:SetType(constants.BulletType.Ice) + gun.bulletNum = gun.bulletNum + constants.SupplyItem[type].num + elseif type == constants.SupplyType.FireGun then + gun:SetType(constants.BulletType.Fire) + gun.bulletNum = gun.bulletNum + constants.SupplyItem[type].num end end end @@ -167,8 +216,11 @@ local function showStartHint() end local function initGame() + content.KillNum = 0 + content.Score = 0 content.BulletList = {} content.MonsterList = {} + content.SupplyList = {} content.PlayerEntity = ECS.CreatePlayer(hazel.CreatePos(constants.TileSize * 16, constants.TileSize * 13)) content.MonsterBirthNum = constants.MonsterBirthInitNum content.GameState = content.GameStateEnum.WaitStart @@ -227,13 +279,56 @@ local function createAnimation(row, time) end) end +---@param num number +---@param x number +---@param y number +local function drawNum(num, x, y) + if num < 0 then + content.NumberTilesheet:Draw(10, 0, hazel.CreateRect(x, y, 32, 32)) + return + end + local scoreStr = tostring(num) + for i = 1, #scoreStr do + local col = tonumber(string.sub(scoreStr, i, i)) + if col == -1 then + col = 10 + end + content.NumberTilesheet:Draw(col, 0, hazel.CreateRect(x + (i - 1) * 32, y, 32, 32)) + end +end + +local function updateRoles() + ---@param monster Entity + for _, monster in pairs(content.MonsterList) do + ---@type RolePropComponent + local roleProp = monster:GetComponent(ECS.ComponentType.RoleProp) + if roleProp and roleProp:IsDie() then + monster:RemoveComponent(ECS.ComponentType.Direction) + monster:RemoveComponent(ECS.ComponentType.AI) + monster:RemoveComponent(ECS.ComponentType.HpShow) + monster:RemoveComponent(ECS.ComponentType.ColliBox) + monster:RemoveComponent(ECS.ComponentType.State) + ---@type ImageComponent + local image = monster:GetComponent(ECS.ComponentType.Image) + image.row = 4 + image.col = 2 + end + end +end + function GameStart() hazel.SetWindowIcon("resources/icon.png") content.Texture = hazel.LoadTexture("resources/tilesheet.png") content.RestartHintTexture = hazel.LoadTexture("resources/RestartHint.png") content.LicensTexture = hazel.LoadTexture("resources/License.png") content.StartHintTexture = hazel.LoadTexture("resources/StartHint.png") - content.Tilesheet = hazel.CreateTileSheet(content.Texture, 3, 10) + content.Tilesheet = hazel.CreateTileSheet(content.Texture, 3, 12) + content.NumberTexture = hazel.LoadTexture("resources/numbers.png") + content.NumberTilesheet = hazel.CreateTileSheet(content.NumberTexture, 11, 1) + hazel.Sound.Load("resources/gameover.wav", constants.SoundName.GameOver) + hazel.Sound.Load("resources/player_hurt.wav", constants.SoundName.PlayerHurt) + hazel.Sound.Load("resources/monster_hurt.wav", constants.SoundName.MonsterHurt) + hazel.Sound.Load("resources/shoot.wav", constants.SoundName.Shoot) initGame() @@ -276,6 +371,17 @@ function GameLoop() hazel.Renderer.DrawTexture(content.LicensTexture, nil, dstrect) end + if content.GameState == content.GameStateEnum.WaitStart or content.GameState == content.GameStateEnum.Gaming then + drawFloors() + updateSupply() + updateMonster() + content.PlayerEntity:Update() + updateBullet() + collisionDeal() + drawCurosr() + updateRoles() + end + ---@type RolePropComponent local playerRoleInfo = content.PlayerEntity:GetComponent(ECS.ComponentType.RoleProp) if not playerRoleInfo or playerRoleInfo.hp <= 0 then @@ -285,13 +391,14 @@ function GameLoop() end end - if content.GameState == content.GameStateEnum.WaitStart or content.GameState == content.GameStateEnum.Gaming then - drawFloors() - updateMonster() - content.PlayerEntity:Update() - updateBullet() - collisionDeal() - drawCurosr() + if content.GameState == content.GameStateEnum.Gaming then + ---@type GunComponent + local gun = content.PlayerEntity:GetComponent(ECS.ComponentType.Gun) + local x = hazel.GetCanvaSize().x - 128 + drawNum(content.Score, x, 32) + if gun then + drawNum(gun:GetBulletNum(), x, 80) + end end if content.GameState == content.GameStateEnum.WaitStart then @@ -320,4 +427,4 @@ function GameQuit() hazel.DestroyTexture(content.RestartHintTexture) hazel.DestroyTexture(content.StartHintTexture) hazel.DestroyTexture(content.LicensTexture) -end +end \ No newline at end of file diff --git a/game/resources/gameover.wav b/game/resources/gameover.wav new file mode 100644 index 0000000..231ae0b Binary files /dev/null and b/game/resources/gameover.wav differ diff --git a/game/resources/monster_hurt.wav b/game/resources/monster_hurt.wav new file mode 100644 index 0000000..a8078ed Binary files /dev/null and b/game/resources/monster_hurt.wav differ diff --git a/game/resources/numbers.png b/game/resources/numbers.png new file mode 100644 index 0000000..3ec61da Binary files /dev/null and b/game/resources/numbers.png differ diff --git a/game/resources/player_hurt.wav b/game/resources/player_hurt.wav new file mode 100644 index 0000000..bc3f3bf Binary files /dev/null and b/game/resources/player_hurt.wav differ diff --git a/game/resources/shoot.wav b/game/resources/shoot.wav new file mode 100644 index 0000000..9c8f421 Binary files /dev/null and b/game/resources/shoot.wav differ diff --git a/game/resources/tilesheet.png b/game/resources/tilesheet.png index 7997510..e23f347 100644 Binary files a/game/resources/tilesheet.png and b/game/resources/tilesheet.png differ diff --git a/hazel/pch.h b/hazel/pch.h index 5f94888..c512981 100644 --- a/hazel/pch.h +++ b/hazel/pch.h @@ -8,6 +8,7 @@ #include #include +#include #include "log.h" #include "glhelper.h" diff --git a/hazel/sound.h b/hazel/sound.h new file mode 100644 index 0000000..3c16379 --- /dev/null +++ b/hazel/sound.h @@ -0,0 +1,13 @@ +#ifndef __HAZEL_SOUND_H__ +#define __HAZEL_SOUND_H__ + +#include + +#include "dllexport.h" +#include "windows.h" +#include "mmsystem.h" + +DLLEXPORT void Hazel_LoadSound(const char* filename, const char* name); +DLLEXPORT void Hazel_PlaySound(const char* name); + +#endif \ No newline at end of file diff --git a/src/sound.c b/src/sound.c new file mode 100644 index 0000000..a836378 --- /dev/null +++ b/src/sound.c @@ -0,0 +1,17 @@ +#include "hazel/sound.h" + +void Hazel_LoadSound(const char* filename, const char* name) { + char buf[1024] = {0}; + snprintf(buf, sizeof(buf), "OPEN %s ALIAS %s", filename, name); + if (mciSendString(TEXT(buf), NULL, 0, NULL)) { + ENGINE_LOG_ERROR("load sound %s (alias %s) failed", filename, name); + } +} + +void Hazel_PlaySound(const char* name) { + char buf[1024] = {0}; + snprintf(buf, sizeof(buf), "PLAY %s FROM 0", name); + if (mciSendString(TEXT(buf), NULL, 0, NULL)) { + ENGINE_LOG_ERROR("play sound %s failed", name); + } +} \ No newline at end of file diff --git a/test/main.c b/test/main.c index 3028069..c6bbdb3 100644 --- a/test/main.c +++ b/test/main.c @@ -1,9 +1,12 @@ #include "hazel/hazel.h" #include "hazel/renderer.h" +#include "hazel/sound.h" Hazel_Texture* texture = NULL; void GameStart() { + Hazel_LoadSound("test/resources/2.wav", "MUSIC1"); + Hazel_PlaySound("MUSIC1"); texture = Hazel_LoadTexture("test/resources/player.png"); Hazel_TextureSetColor(texture, 0, 1, 0); Hazel_TextureSetAlpha(texture, 0.3); diff --git a/test/resources/1.wav b/test/resources/1.wav new file mode 100644 index 0000000..b3ea038 Binary files /dev/null and b/test/resources/1.wav differ diff --git a/test/resources/2.wav b/test/resources/2.wav new file mode 100644 index 0000000..62351fa Binary files /dev/null and b/test/resources/2.wav differ