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