使 Sprite 在 Lua 中消失

Making a Sprite Disappear in Lua

这里是 Lua 和 Love2D 的新手!我正在尝试完成我正在参加的在线课程的期末项目,我想知道如何让精灵消失。目前,我拥有它,所以玩家在一个 class 中,而与地图(如瓷砖和背景)有关的任何东西都在另一个中。我想这样写,如果玩家击中特定的方块,则该方块消失,并且计数器上升。我知道关于 cs50 gamedev course with balls 也有类似的问题,但我不太了解代码,所以我希望有人能帮助我。我的 map.lua 看起来像这样:

require 'Util'
require 'Player'

Map = Class{}

-- Storing where all the sprites are in the tile spritesheet
EMPTY = 1
LID = 2
SUSHI1 = 3
SUSHI2 = 4

-- Storing where the background should be
BACKGROUND = 1

-- Scroll speed for the camera
local SCROLL_SPEED = 200

function Map:init()

    -- Storing the spritesheet
    self.background = love.graphics.newImage('Graphics/platform/NEW/back2.png')
    self.spritesheet = love.graphics.newImage('Graphics/platform/newplatforms/all.png')

    -- Storing the width and height of each sprite AND background width and height
    self.backgroundWidth = 500
    self.backgroundHeight = 2000
    self.tileWidth = 35
    self.tileHeight = 15

    -- Creating a player object so that it has access to the map properties
    -- Passing in the self object for that specific reason
    self.player = Player(self)

    -- Table to contain all the tile sprites
    self.tiles = {}
    -- Table to contain all the background sprites
    self.back = {}

    -- Stores the map width and height
    -- NOTE that these values are chosen through experimentation
    self.mapWidth = 15
    self.mapHeight = 133

    -- Cutting the sprites of the sheet AND background
    self.backgroundSprites = generateQuads(self.background, self.backgroundWidth, self.backgroundHeight)
    self.tileSprites = generateQuads(self.spritesheet, self.tileWidth, self.tileHeight)

    -- Setting the camera values
    self.camX = 0
    self.camY = self.backgroundHeight - VIRTUAL_HEIGHT

    -- Setting the background tiles
    for y = 1, self.mapHeight do
        for x = 1, self.mapWidth do
            self:setTileBackground(x, y, BACKGROUND)
        end
    end

    -- Setting all the tiles initially to be empty
    for y = 1, self.mapHeight do
        for x = 1, self.mapWidth do
            self:setTile(x, y, EMPTY)
        end
    end

    -- Setting the initial floor
    for x = 1, self.mapWidth do
        self:setTile(x, self.mapHeight, LID)
    end

    ------------------------------------------------- START OF MAP CREATION -------------------------------------------------
    local y = 1
    -- variable to help with the twist
    local location = 5
    while y < self.mapHeight do
        -- Make sure we're at least 6 tiles away from the bottom
        -- START OF EVERYTHING
        if y < self.mapHeight - 1 and y > 3 then
            -- FOR THE FIRST QUARTER OF THE MAP
            if y > self.mapHeight * 2/3 then
                
                for counter = 1, self.mapWidth / 2.5 do

                    -- As long as location isn't max, keep incrementing
                    -- NOTICE THE - 7 TO KEEP IT ON ONE SIDE
                    if location < self.mapWidth - 7 then
                        location = location + 1
                    -- If location goes to max, then reset
                    else
                        location = 1
                    end
                 
                    -- Set the tile in the appropriate location
                    self:setTile(location, y, LID)
                end

            -- FOR THE SECOND QUARTER OF THE MAP, HARDER
            elseif y < self.mapHeight * 2/3 and y > self.mapHeight * 1/3 then
                
                for counter = 1, self.mapWidth / 2.5 do

                    -- As long as location isn't max, keep incrementing
                    if location < self.mapWidth then
                        location = location + 1
                    -- If location goes to max, then reset
                    else
                        location = 1
                    end
                 
                    -- Set the tile in the appropriate location
                    -- CHANGING BETWEEN SUSHI COVERS
                    if (counter % 2 == 0) then
                        self:setTile(location, y, SUSHI1)
                    else
                        self:setTile(location, y, SUSHI2)
                    end
                
                end
                
            -- FOR THE LAST QUARTER OF THE MAP
            else
                for counter = 1, self.mapWidth / 2.5 do

                    -- As long as location isn't max, keep incrementing
                    if location < self.mapWidth then
                        location = location + 1
                    -- If location goes to max, then reset
                    else
                        location = 1
                    end
                 
                    -- Set the tile in the appropriate location
                    -- CHANGING BETWEEN SUSHI COVERS
                    if (counter % 2 == 0) then
                        self:setTile(location, y, SUSHI1)
                    else
                        self:setTile(location, y, SUSHI2)
                    end

                end
            end
        end
        -- INCREMENTING SCANLINE
        y = y + 4
    end

end

-- Function to 'Set' what the background tiles should be in the map
function Map:setTileBackground(x, y, tile)
    self.back[(y - 1) * self.mapWidth + x] = tile
end

-- Function to 'Set' what the tiles should be in the map
function Map:setTile(x, y, id)
    self.tiles[(y - 1) * self.mapWidth + x] = id
end

-- Function to get the ID of the tile at a certain location
-- This passes the map in as pixel coordinates rather than tile IDs
-- The x and y here are pixel values, getting the tile at that pixel location
-- Note that getTile gets the tile as a TILE, and so needed adjustment
-- The + 1 is because pixel values would make it go back to a pixel based system, and that's
-- 0 indexed. Instead, we want it to be 1 indexed
function Map:tileAt(x, y)
    return {
        x = math.floor(x / self.tileWidth) + 1,
        y = math.floor(y / self.tileHeight) + 1,
        id = self:getTile(math.floor(x / self.tileWidth) + 1, math.floor(y / self.tileHeight) + 1)
    }
end

-- Function to figure out what background tile should be in the map
function Map:getTileBackground(x, y)
    return self.back[(y - 1) * self.mapWidth + x]
end

-- Function to figure out what tile should be in the map
function Map:getTile(x, y)
    return self.tiles[(y - 1) * self.mapWidth + x]
end

-- Function to define what the collidables are
function Map:collides(tile)
    -- Define the collidable tiles. It contains the
    -- constants of the tiles that are considered solid
    local collidables = {
        LID, SUSHI1, SUSHI2
    }

    -- This is the function that will be used to check if the tile id that's under us matches 
    --the collidables. It's essentially saying for every key/value pair for ipairs which is 
    -- iterating through all the k/v pairs, and the _ just means index. We aren't using the
    -- key here so it doesn't matter anyway
    for _, v in ipairs(collidables) do
        if tile.id == v then
            return true
        end
    end

    -- Return false if it isn't a collidable
    return false
end

function Map:update(dt)
    -- Updating the player
    self.player:update(dt)

    -- keep camera's coordinate following the player, preventing camera from
    -- scrolling past 0 to the left and the map's width
    self.camX = math.max(0, math.min(self.player.x - VIRTUAL_WIDTH / 2,
        math.min(self.backgroundWidth - VIRTUAL_WIDTH, self.player.x)))

    self.camY = math.max(0, math.min(self.player.y - VIRTUAL_HEIGHT * 0.815, 
        math.max(self.backgroundHeight - VIRTUAL_HEIGHT, self.player.y)))
end

function Map:render()
    for y = 1, self.mapHeight do
        for x = 1, self.mapWidth do
            love.graphics.draw(self.background, self.backgroundSprites[self:getTileBackground(x, y)],
                (x - 1) * self.backgroundWidth, (y - 1) * self.backgroundHeight)
            love.graphics.draw(self.spritesheet, self.tileSprites[self:getTile(x, y)],
                (x - 1) * self.tileWidth, (y - 1) * self.tileHeight)
        end
    end

    self.player:render()
end

我的 player.lua 文件如下所示:

Player = Class{}

require 'Animation'

-- Know where everything is in the sheet
STEVEN_JUMP = 1
STEVEN_IDLE = 2
STEVEN_WALK1 = 3
STEVEN_WALK2 = 4

-- Setting the movement speed
local MOVE_SPEED = 100

-- Jump velocity, gravity velocity
local JUMP_VELOCITY = 700
local GRAVITY = 40


function Player:init(map)

    -- Initializing the x and y coordinates of the character
    -- NOTE that for the y to be at the bottom, we must subtract the 30 of the platform, and 30
    -- of Steven himself
    self.x = map.tileWidth * 5 -- Essentially 300 pixels to the right
    self.y = map.backgroundHeight - 45

    -- Storing the width and height of the spritesheet
    self.width = 32
    self.height = 30

    -- The dx and dy variables
    self.dx = 0
    self.dy = 0

    -- Easier to type this way
    self.map = map

    -- Storing the spritesheet for Steven
    self.playerSheet = love.graphics.newImage('Graphics/platform/steven/all.png')

    -- Splicing up the spritesheet
    self.frames = generateQuads(self.playerSheet, self.width, self.height)

    -- INITIAL STATE
    self.state = 'idle'

    -- DIRECTION THE CHARACTER IS FACING, DEFAULT
    self.direction = 'right'

    --Animation table
    -- Each takes as an argument the parameters that we used in the Animation
    -- function in the next page, and so it needs textures, frames, etc 
    self.animations = {
        -- IDLE ANIMATION TABLE
        -- Notice that the {} is meant to be an argument into the Animation function,
        -- or class, but since it's only a table, we don't need to put ({}) but can just
        -- put {}
        ['idle'] = Animation {
            -- The texture that we'll use
            texture = self.texture,
            -- The frames that we're going to use
            frames = {
                self.frames[2]
            },
            -- Interval between frames
            interval = 1
        },

        ['walking'] = Animation {
            texture = self.texture,
            frames = {
                self.frames[3], self.frames[4]
            },
            interval = 0.15
        },

        ['jumping'] = Animation {
            texture = self.texture,
            frames = {
                self.frames[1]
            },
            interval = 1
        }
    }

    -- What the current animation is
    self.animation = self.animations['idle']
    self.currentFrame = self.animation:getCurrentFrame()

    -- Checking the states, returns a function as keys
    self.behaviors = {
        ['idle'] = function(dt)
            -- JUMPING STEVEN
            -- Notice how we need to create a wasPressed function as it's a one-time press
            if love.keyboard.wasPressed('space') then
                --  Set the velocity upwards so negative
                self.dy = -JUMP_VELOCITY
                -- Change the state
                self.state = 'jumping'
                -- Change the animation
                self.animation = self.animations['jumping']
            -- STEVEN MOVES
            elseif love.keyboard.isDown('a') then
                -- Change the direction
                self.direction = 'left'
                -- left movement
                self.dx = -MOVE_SPEED
                -- Change the state
                self.state = 'walking'
                -- Resetting the animation
                self.animations['walking']:restart()
                -- Change the animation
                self.animation = self.animations['walking']
            elseif love.keyboard.isDown('d') then
                -- Change the direction
                self.direction = 'right'
                -- right movement
                self.dx = MOVE_SPEED
                -- Change the state
                self.state = 'walking'
                -- Reset the animation
                self.animations['walking']:restart()
                -- Change the animation
                self.animation = self.animations['walking']
            else
                -- If we aren't pressing a or d, make him idle
                self.dx = 0
            end
        end,
        ['walking'] = function(dt)
            if love.keyboard.wasPressed('space') then
                --  Set the velocity upwards so negative
                self.dy = -JUMP_VELOCITY
                self.state = 'jumping'
                -- Change the animation
                self.animation = self.animations['jumping']
            -- STEVEN MOVES
            elseif love.keyboard.isDown('a') then
                -- Change the direction
                self.direction = 'left'
                -- left movement
                self.dx = -MOVE_SPEED
                -- Change the animation
                self.animation = self.animations['walking']
            elseif love.keyboard.isDown('d') then
                -- Change the direction
                self.direction = 'right'
                -- right movement
                -- Notice we add 80 to account for that weird slow-mo here
                self.dx = MOVE_SPEED
                -- Change the animation
                self.animation = self.animations['walking']
            else
                -- If we aren't pressing a or d, make him idle
                self.dx = 0
                self.state = 'idle'
                self.animation = self.animations['idle']
            end

            -- check if there's a tile directly beneath us
            if not self.map:collides(self.map:tileAt(self.x, self.y + self.height)) and
                not self.map:collides(self.map:tileAt(self.x + self.width - 1, self.y + self.height)) then
                
                -- if so, reset velocity and position and change state
                self.state = 'jumping'
                self.animation = self.animations['jumping']
            end
        end,
        ['jumping'] = function(dt)

            ------------------------------------------- TO DO IF PLAYER GOES UNDER CAMERA BOTTOM ------------------------------------------------------------
            -- break if we go below the surface
            if self.y > self.map.backgroundHeight then

            end

            -- This is to change and manuever in the air
            if love.keyboard.isDown('a') then
                self.direction = 'left'
                -- Change the moving velocity
                self.dx = -MOVE_SPEED
            elseif love.keyboard.isDown('d') then
                self.direction = 'right'
                -- Change the moving velocity
                self.dx = MOVE_SPEED
            end
            -- Setting the y velocity
            self.dy = self.dy + GRAVITY

            -- check if there's a tile directly beneath us
            if self.map:collides(self.map:tileAt(self.x, self.y + self.height)) or
                self.map:collides(self.map:tileAt(self.x + self.width - 1, self.y + self.height)) and self.dy < 0 then

                -- if so, reset velocity and position and change state
                self.dy = 0
                self.state = 'idle'
                self.animation = self.animations['idle']
                self.y = (self.map:tileAt(self.x, self.y + self.height).y - 1) * self.map.tileHeight - self.height
            end
        end
    }

end

function Player:update(dt)
    -- Calling the behaviors as a function
    self.behaviors[self.state](dt)

    --Updating the animation
    self.animation:update(dt)

    -- Get the current frame
    self.currentFrame = self.animation:getCurrentFrame()

    -- Changing the velocities
    self.x = self.x + self.dx * dt
    self.y = self.y + self.dy * dt
end

function Player:render()
    
    -- NOTE the X scaling factor, which is -1 as in to flip if it's right
    local scaleX
    if self.direction == 'right' then
        scaleX = 1
    else
        scaleX = -1
    end

    -- Drawing the character initially
    -- NOTE the extra arguments to change the rotation and OFFSET/FLIP
    -- 0 is the initial rotation, which we don't want
    -- Scaling by -1 changes it to flipping to their right corner, not left, and so
    -- we need to move the origin point (which is initially the top left) to the center
    -- as we want it to change place wrt the origin
    -- The 1 is scaling 1 by Y
    -- The last 2 arguments are adding the offset of whatever its width and height are
    -- ALSO need to account for the origin (since it draws it from the origin) to make
    -- sure Steven isn't floating
    love.graphics.draw(self.playerSheet, self.currentFrame, math.floor(self.x + self.width / 2), math.floor(self.y + self.height / 2),
        0, scaleX, 1,
        self.width / 2, self.height / 2)

end

我想做的是,如果玩家跳到 SUSHI2 方块上,它就会消失。

抱歉这么久 post。我真的很想尽我最大的努力,我正在尝试添加尽可能多的普通游戏,但我是 gamelogic 的新手,我不确定如何从屏幕上“删除”一个图块。我问这主要是为了稍后实现如何在敌人死亡时移除整个敌人,这样它就会有某种健康栏,当它达到 0 时,角色将不再出现在屏幕上,因为我'假设这将是相同的逻辑。或者,一旦一些子弹击中敌人,它们就会从屏幕上消失。无论如何,我们将不胜感激! (我附上了一张游戏现在的样子的图片,如果有帮助的话,我指的是哪个方块) meow meow

您的代码似乎可以处理碰撞,我假设它可以工作,我的猜测是将您碰撞的 SHUSHI2 单元格替换为 EMPTY 网格中的单元格值,因此它会被 销毁 。假设你想在坠落时摧毁一个 SUSHI2 方块,你可以采用以下解决方案:

--> Player.behaviors.walking :

    -- check if there's a tile directly beneath us

    -- Let's add a local for the example
    local tile_we_re_falling_on = self.map:tileAt(self.x, self.y + self.height)

    if self.map:collides(tile_we_re_falling_on) or
        self.map:collides(self.map:tileAt(self.x + self.width - 1, self.y + self.height)) and self.dy < 0 then
        if self.dy > 0 and tile_we_re_falling_on.id == SUSHI2 then -- We're falling on a SUSHI2 block/tile
            -- Overwrite the tile to EMPTY
            self.map:setTile(tile_we_re_falling_on.x, tile_we_re_falling_on.y, EMPTY)
            -- Increment some counter, maybe?
        end

        -- if so, reset velocity and position and change state
        self.dy = 0
        self.state = 'idle'
        self.animation = self.animations['idle']
        self.y = (self.map:tileAt(self.x, self.y + self.height).y - 1) * self.map.tileHeight - self.height
    end

希望我理解了您的问题,希望对您有所帮助。您可能想要清理它,感觉有点老套,但展示了这个想法。

另一件事是,当您在游戏 tick 中改变世界时,其他一些代码可能会假设它没有改变,这可能会导致一些奇怪的行为。当所有更新逻辑具有 运行 而不是 in-between 更新步骤时,一种常见的做法是 延迟 在帧的 begin/end 改变世界,也就是说,您将保存 tile id (x=2, y=43) 需要更改为 EMPTY 的事实,并将在更新函数结束时应用更改。在这种情况下,我相信它会很好。