跳转至内容

21. Exercises,对象管理,组合

3.17,补完 exercises。

Exercise 44

创建三个 Room:CircleRoom,在屏幕中央画一个圆;RectangleRoom,在屏幕中央画一个矩形;以及 PolygonRoom,在屏幕中央画一个多边形。再把 F1F2F3 绑定成切换到这三个 Room 的按键。

lua
function love.load()
    local room_files = {}
    recursive_enumerate("rooms", room_files)
    require_files(room_files)

    input = input_handler()
    timer = enhanced_timer()

    current_room = nil
    input:bind("f1", function() goto_room("circle_room") end)
    input:bind("f2", function() goto_room("rectangle_room") end)
    input:bind("f3", function() goto_room("polygon_room") end)
end

function love.update(dt)
    if current_room then current_room:update(dt) end
end

function love.draw()
    if current_room then current_room:draw() end
end

function goto_room(room_type, ...)
    current_room = _G[room_type](...)
end
lua
circle_room = object:extend()

function circle_room:new()
end

function circle_room:update(dt)
end

function circle_room:draw()
    love.graphics.circle("fill", 400, 300, 50)
end
lua
rectangle_room = object:extend()

function rectangle_room:new()
end

function rectangle_room:update(dt)
end

function rectangle_room:draw()
    love.graphics.rectangle("fill", 400 - 100/2, 300 - 50/2, 100, 50)
end
lua
polygon_room = object:extend()

function polygon_room:new()
end

function polygon_room:update(dt)
end

function polygon_room:draw()
    love.graphics.polygon(
        "fill",
        400, 300 - 50,
        400 + 50, 300,
        400, 300 + 50,
        400 - 50, 300
    )
end

Exercise 48

创建一个带有 AreaStage Room。然后再创建一个继承自 GameObjectCircle 对象,并让 Stage 每隔 2 秒在随机位置生成一个这样的实例。每个 Circle 实例都应该在 2 到 4 秒之间的随机时间后自我销毁。

game_object + area + room 三层解耦:

  • game_object 只管自己 + 一个局部 timer
  • area 管一堆对象,统一 update / draw 和清理 dead
  • room(stage_with_area)组合 area + 自己的 timer 做关卡逻辑
lua
game_object = object:extend()

function game_object:new(area, x, y, opts)
    local opts = opts or {}
    if opts then
        for k, v in pairs(opts) do
            self[k] = v
        end
    end

    self.area = area
    self.x, self.y = x, y
    self.id = uuid()
    self.timer = enhanced_timer()
    self.dead = false
end

function game_object:update(dt)
    if self.timer then self.timer:update(dt) end
end
lua
area = object:extend()

function area:new(room)
    self.room = room
    self.game_objects = {}
end

function area:update(dt)
    for i = #self.game_objects, 1, -1 do
        local obj = self.game_objects[i]
        obj:update(dt)
        if obj.dead then table.remove(self.game_objects, i) end
    end
end

function area:draw()
    for _, obj in ipairs(self.game_objects) do
        obj:draw()
    end
end

function area:add_game_object(game_object_type, x, y, opts)
    local game_object = _G[game_object_type](self, x, y, opts)
    table.insert(self.game_objects, game_object)
    return game_object
end
lua
stage_with_area = object:extend()

function stage_with_area:new()
    self.area = area(self)
    self.timer = enhanced_timer()
    self.timer:every(2, function()
        self.area:add_game_object('circle', random(0, 800), random(0, 600))
    end)
end

function stage_with_area:update(dt)
    self.area:update(dt)
    self.timer:update(dt)
end

function stage_with_area:draw()
    self.area:draw()
end
lua
circle = game_object:extend()

function circle:new(area, x, y, opts)
    circle.super.new(self, area, x, y, opts)
    self.timer:after(random(2, 5), function() self.dead = true end)
end

function circle:update(dt)
    circle.super.update(self, dt)
end

function circle:draw()
    love.graphics.circle("fill", self.x, self.y, 50)
end

Exercise 49

创建一个不带 AreaStage Room。然后创建一个不继承 GameObjectCircle 对象,并让 Stage 每隔 2 秒在随机位置生成一个实例。这个 Circle 实例也应该在 2 到 4 秒之间的随机时间后自行消失。

不用 Area 自己手写管理所有游戏对象。

lua
stage_without_area = object:extend()

function stage_without_area:new()
    self.game_objects = {}
    self.timer = enhanced_timer()
    self.timer:every(2, function()
        table.insert(self.game_objects, circle(self, random(0, 800), random(0, 600)))
    end)
end

function stage_without_area:update(dt)
    for i = #self.game_objects, 1, -1 do
        local obj = self.game_objects[i]
        obj:update(dt)
        if obj.dead then table.remove(self.game_objects, i) end
    end
    self.timer:update(dt)
end

function stage_without_area:draw()
    for _, obj in ipairs(self.game_objects) do
        obj:draw()
    end
end
lua
circle = object:extend()

function circle:new(x, y)
    self.x, self.y = x, y
    self.dead = false
    self.timer = enhanced_timer()
    self.timer:after(random(2, 4), function() self.dead = true end)
end

function circle:update(dt)
    self.timer:update(dt)
end

function circle:draw()
    love.graphics.circle("fill", self.x, self.y, 50)
end

Exercise 50

第 1 题的解法里引入过一个 random 函数。请扩展这个函数,让它在只传一个参数时,也能正常工作,并返回一个介于 0 和该参数之间的随机实数。另外,再扩展一下,让 minmax 可以反着传,也就是第一个值允许比第二个值更大。

单参时视为 0..max,并允许 min > max 的情况先交换。这样调用更自由,但会让“传参错误”更难被发现,调试时要有心理准备。

lua
function random(min, max)
    if not max then
        max = min
        min = 0
    end

    if min > max then
        min, max = max, min
    end

    return love.math.random() * (max - min) + min
end