## My own action adventure engine

VideroBoy
Party member
Posts: 102
Joined: Wed Mar 31, 2010 6:12 pm

### My own action adventure engine

A project I hope is going somewhere.
SpaceNinja.love
Some issues though:
• When the background music finishes, the game crashes.
• I can't get collisions with wall corners working. My code for corners is based on Gamasutra's article on circle/sphere collisions (I'm treating the corner as a zero-radius circle) and my test looks like this:

Code: Select all

-- collision.lua

-- "line" represents movement vector
-- "px" and "py" is corner
local function pointCollision(line, px, py, radius)
local result -- Time of collision, or nil if no collision will happen

local dx = line.xf - line.xi
local dy = line.yf - line.yi

local moveLenSq = (dx * dx) + (dy * dy)

-- Center of circle to point
-- I'm just going to call this vector "v"
local vx = px - line.xi
local vy = py - line.yi

local moveLen = math.sqrt(moveLenSq)

local ndx = dx / moveLen
local ndy = dy / moveLen

-- Dot product between v and normal of movement vector.
-- This is equal to the length of the projection vector of
-- v onto the movement vector
local dotprod = (vx * ndx) + (vy * ndy)
assert(dotprod > 0) -- Circle should already be moving towards
-- corner, since it's moving towards the
-- corner's line (this function called by
-- line segment collision function)

-- Closest distance circle will ever get to point
local vLenSq = (vx * vx) + (vy * vy)
local closestDistSq = vLenSq - (dotprod * dotprod)

local realMoveLen = dotprod - math.sqrt(moveOffsetSq)

if realMoveLen < moveLen then
result = realMoveLen / moveLen
end
end

return result
end
Last edited by VideroBoy on Fri Dec 03, 2010 6:02 pm, edited 3 times in total.

Kingdaro
Party member
Posts: 395
Joined: Sun Jul 18, 2010 3:08 am

### Re: Action adventure game map test

Found a bug where in some instances you can walk through walls:
love 2010-09-02 15-23-32-11.png (3.73 KiB) Viewed 4751 times
love 2010-09-02 15-23-40-97.png (3.91 KiB) Viewed 4751 times
(Windows 7) After it seems that the music ends, love.exe has stopped working.

Jasoco
Inner party member
Posts: 3651
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

### Re: Action adventure game map test

You should take a look at my project (Adventure Game, GIT link in signature) for tips on how to do maps and collisions. Sorry for the lack of comments and mass of code, but it's a big project.

VideroBoy
Party member
Posts: 102
Joined: Wed Mar 31, 2010 6:12 pm

### Re: Action adventure game map test

Kingdaro wrote:Found a bug where in some instances you can walk through walls

(Windows 7) After it seems that the music ends, love.exe has stopped working.
I said there were these bugs in my first post.
Any suggestions on how to fix them?

Jasoco, what kind of collision detection does your game do? In my game the movable character uses a bounding circle. To test against the terrain I get any wall the character may intersect and generate lines for their sides to test against. I'm using a continuous collision detection method where I test when the character's bounding circle will make contact with a wall line or corner before moving the character.

Jasoco
Inner party member
Posts: 3651
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

### Re: Action adventure game map test

Is your game tile based? Or are you trying to check for collisions with arbitrarily located shapes that could be anywhere?

Mine is tile based which is easy as checking if the players X is overlapping a tile with a solid flag. (Hit map)

VideroBoy
Party member
Posts: 102
Joined: Wed Mar 31, 2010 6:12 pm

### Re: Action adventure game map test

My game is tile based. It's just the controllable character that can be placed on any arbitrary (pixel) location.

I check for collisions before they happen, then move the character by the right amount. For the wall sides, I find out if collisions will occur by getting the lines segments bordering the walls the character might hit. Wall side collision is working, but not wall corner collision.

If I were to post code, how much would people be willing to look at?

Jasoco
Inner party member
Posts: 3651
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

### Re: Action adventure game map test

You need to check:

tile[floor(playerX / tileWidth)][floor(playerY / tileHeight)] == not solid

If the above array check is not a solid object, then you can walk there. Else you can't. But you need to check for all four corners of the player. (I know you want to use a circle, so it will be a different method) If you use a square, it would be playerX, playerY : playerX + playerW, playerY : playerX + playerW, playerY + playerH : playerX, playerY + playerH. For a circle, well that would be more of a distance thing, but I can't help you there. It's easier to use a square. Will your player really be a circle?

VideroBoy
Party member
Posts: 102
Joined: Wed Mar 31, 2010 6:12 pm

### Re: Action adventure game map test

Jasoco wrote:You need to check:

tile[floor(playerX / tileWidth)][floor(playerY / tileHeight)] == not solid
I'm already doing that in a way.

I think we're talking past each other, so I'm just going to post my collision code in full.

First of all, keep in mind that I'm using a fixed timestep, using a method described here. So, I don't multiply anything by dt.

My update function looks like this:

Code: Select all

local dx, dy = actor.getVelocity(player, currentButtons)
local time = collision.actorVSLevel(currentLevel, player, dx, dy)
actor.move(player, dx, dy, time)

First I get the player's velocity. Then I get a time value where I check the player against the map. This value is between zero and one; one if the player is not going to collide with anything, zero if the player is already up against something. So, time represents the moment in the current frame that the player collides with something. Note that the player has not actually moved yet. Movement only happens in the last function, which looks like this:

Code: Select all

function actor.move(act, dx, dy, time)
local time = time or 1.0

act.x = act.x + math.floor(dx * time)
act.y = act.y + math.floor(dy * time)
end

This is what checking the player against the map looks like. The idea is I find the time, a value between zero and one, that the earliest collision occurs. I return 1 if I don't find any collision.

Code: Select all

function collision.actorVSLevel(level, act, dx, dy, time)
local resultTime = time or 1.0

act.x, act.y, dx, dy)

local possibleTiles = levels.tilesInRect(level, moveRect)
local moveLine = {xi = act.x, yi = act.y,
xf = act.x + dx, yf = act.y + dy}

for _, tile in ipairs(possibleTiles) do
if levels.isWall(level, tile.x, tile.y) then
if dx < 0 then
-- Going west
local line = levels.lineSegmentEast(level,
tile.x, tile.y)
local normal = {x = 1, y = 0}

local firstCorner = levels.hasNorthEastCorner(level,
tile.x, tile.y)
local secondCorner = levels.hasSouthEastCorner(level,
tile.x, tile.y)

normal, firstCorner, secondCorner)

if iTime and (iTime < resultTime) then
resultTime = iTime
end
elseif dx > 0 then
-- Going east
local line = levels.lineSegmentWest(level,
tile.x, tile.y)
local normal = {x = -1, y = 0}

local firstCorner = levels.hasNorthWestCorner(level,
tile.x, tile.y)
local secondCorner = levels.hasSouthWestCorner(level,
tile.x, tile.y)

normal, firstCorner, secondCorner)

if iTime and (iTime < resultTime) then
resultTime = iTime
end
end

if dy < 0 then
-- Going north
local line = levels.lineSegmentSouth(level,
tile.x, tile.y)
local normal = {x = 0, y = 1}

local firstCorner = levels.hasSouthWestCorner(level,
tile.x, tile.y)
local secondCorner = levels.hasSouthEastCorner(level,
tile.x, tile.y)

normal, firstCorner, secondCorner)

if iTime and (iTime < resultTime) then
resultTime = iTime
end
elseif dy > 0 then
-- Going south
local line = levels.lineSegmentNorth(level,
tile.x, tile.y)
local normal = {x = 0, y = -1}

local firstCorner = levels.hasNorthWestCorner(level,
tile.x, tile.y)
local secondCorner = levels.hasNorthEastCorner(level,
tile.x, tile.y)

normal, firstCorner, secondCorner)

if iTime and (iTime < resultTime) then
resultTime = iTime
end
end
end
end

return resultTime
end

First thing I do is get the rectangle that encloses the player's movement, which looks like this:
move_rect.png (18.01 KiB) Viewed 4694 times

Code: Select all

function collision.circleMoveRect(radius, x, y, dx, dy)
local width = (radius * 2) + math.abs(dx)
local height = (radius * 2) + math.abs(dy)

local rx = x - radius
if dx < 0 then
rx = rx + dx
end

local ry = y - radius
if dy < 0 then
ry = ry + dy
end

-- return {x = x or 0, y = y or 0,
--   width = width or 0, height = height or 0}
return utility.rectangle(rx, ry, width, height)
end

Then I get the tiles covered by the movement rectangle:

Code: Select all

function levels.tilesInRect(level, rect)
local minX = levels.tileX(level, rect.x)
local minY = levels.tileY(level, rect.y)
local maxX = levels.tileX(level, rect.x + rect.width)
local maxY = levels.tileY(level, rect.y + rect.height)

if minX < 0 then
minX = 0
end

if minY < 0 then
minY = 0
end

if maxX >= level.width then
maxX = level.width - 1
end

if maxY >= level.height then
maxY = level.height - 1
end

local result = {}

for x = minX, maxX do
for y = minY, maxY do
table.insert(result, {x = x, y = y})
end
end

return result
end

Now that I have all the tiles the player will cross, I loop through them and find any wall tiles. When i find a wall tile, I figure out which sides the play may hit. So, if the player is moving east, I get a line segment representing the wall's west side.

Code: Select all

function levels.lineSegmentNorth(level, x, y)
-- Goes west to east
local xi = x * TILE_SIZE
local yi = y * TILE_SIZE
local xf = xi + TILE_SIZE
local yf = yi

return {xi = xi, yi = yi, xf = xf, yf = yf}
end

function levels.lineSegmentSouth(level, x, y)
-- Goes west to east
local xi = x * TILE_SIZE
local yi = (y * TILE_SIZE) + TILE_SIZE
local xf = xi + TILE_SIZE
local yf = yi

return {xi = xi, yi = yi, xf = xf, yf = yf}
end

function levels.lineSegmentEast(level, x, y)
-- Goes north to south
local xi = (x * TILE_SIZE) + TILE_SIZE
local yi = y * TILE_SIZE
local xf = xi
local yf = yi + TILE_SIZE

return {xi = xi, yi = yi, xf = xf, yf = yf}
end

function levels.lineSegmentWest(level, x, y)
-- Goes north to south
local xi = x * TILE_SIZE
local yi = y * TILE_SIZE
local xf = xi
local yf = yi + TILE_SIZE

return {xi = xi, yi = yi, xf = xf, yf = yf}
end

I also check whether or not the wall tile has any corners facing the player. That is, I don't check the player against the wall tile's corners if the wall tile is part of a larger wall.

Code: Select all

function levels.hasNorthEastCorner(level, x, y)
return not ( levels.isWall(level, x, y - 1)
or levels.isWall(level, x + 1, y) )
end

function levels.hasSouthEastCorner(level, x, y)
return not ( levels.isWall(level, x, y + 1)
or levels.isWall(level, x + 1, y) )
end

function levels.hasNorthWestCorner(level, x, y)
return not ( levels.isWall(level, x, y - 1)
or levels.isWall(level, x - 1, y) )
end

function levels.hasSouthWestCorner(level, x, y)
return not ( levels.isWall(level, x, y + 1)
or levels.isWall(level, x - 1, y) )
end

I finally check the player against a line segment representing a wall side.

Code: Select all

local function lineCollision(a, b, radius, bNormal, bHasFirstPoint,
bHasSecondPoint)
local result = nil

local aDX = a.xf - a.xi
local aDY = a.yf - a.yi

local bDX = b.xf - b.xi
local bDY = b.yf - b.yi

-- Shift wall towards circle
local rx = radius * bNormal.x
local ry = radius * bNormal.y

local crossX = a.xi - b.xi - rx
local crossY = a.yi - b.yi - ry

local intersectA = ((bDX * crossY) - (bDY * crossX)) / denom
local intersectB = ((aDX * crossY) - (aDY * crossX)) / denom

if (intersectA >= 0) then
-- Circle will cross plane of wall

if (intersectA <= 1)
and (intersectB >= 0) and (intersectB <= 1) then
-- Circle hitting wall itself
result = intersectA
else
-- Test against wall's end points

if bHasFirstPoint and (intersectB < 0) then
-- Test against first point
result = pointCollision(a, b.xi, b.yi, radius)
elseif bHasSecondPoint and (intersectB > 1) then
-- Test against second point
result = pointCollision(a, b.xf, b.yf,
end
end
end

return result
end

The first part is a standard line intersection test, based on this geometry article. Since the wall line is always horizontal or vertical, I just have to shift it towards the player by the player's radius. intersectA corresponds to when the (infinitely extended) movement line will intersect the (infinitely extended) wall line. If it's between 0 and 1, the intersection will happen during this frame. If it's less than 0, the wall only intersects with the movement line extended backwards from the movement line segment, corresponding to sometime in the past. If intersectA is greater than 1, the two lines will intersect but only in the future. intersectA is treated like a time value.

When the two lines are not intersecting is when I try to test against the corners, and this is the part I'm struggling with. I'll post my function for testing against a corner point again.

Code: Select all

local function pointCollision(line, px, py, radius)
local result

local dx = line.xf - line.xi
local dy = line.yf - line.yi

local moveLenSq = (dx * dx) + (dy * dy)

-- Center of circle to point
-- I'm just going to call this vector "v"
local vx = px - line.xi
local vy = py - line.yi

local moveLen = math.sqrt(moveLenSq)

local ndx = dx / moveLen
local ndy = dy / moveLen

-- Dot product between v and normal of movement vector.
-- This is equal to the length of the projection vector of
-- v onto the movement vector
local dotprod = (vx * ndx) + (vy * ndy)
assert(dotprod > 0) -- Circle should already be moving towards
-- corner, since it's moving towards the
-- corner's line

-- Closest distance circle will ever get to point
local vLenSq = (vx * vx) + (vy * vy)
local closestDistSq = vLenSq - (dotprod * dotprod)

local realMoveLen = dotprod - math.sqrt(moveOffsetSq)

if realMoveLen < moveLen then
result = realMoveLen / moveLen
end
end

return result
end

And the article I got my algorithm from:
http://www.gamasutra.com/view/feature/3 ... php?page=2

I am ambitious about my collision detection you can tell.

VideroBoy
Party member
Posts: 102
Joined: Wed Mar 31, 2010 6:12 pm

### Re: Action adventure game map test

I decided to switch to using a bounding box for the player. It actually did simplify my code since I'm assuming horizontal and vertical lines only, though the implementation process was harder that I would have thought.

I also figured out the music crash thing. I didn't set the music variable to looping nor bind it to a variable outside the load function's scope, so it got garbage collected when the song finished.

I've also updated the player sprite to better reflect the dimensions I intend my game characters to have and made the walls less "sticky" like they were in the first demo.
You can go into praise mode now.
Attachments
SpaceNinja.love

thelinx
The Strongest
Posts: 848
Joined: Fri Sep 26, 2008 3:56 pm
Location: Sweden

### Re: Action adventure game map test

Your player character suffers from PO2 syndrome.

### Who is online

Users browsing this forum: No registered users and 32 guests