1

Hi guys.
I'm making a top-down style action game.
But, my implementation of the diagonal movement and the collision detection does not work as desired.

Cart #kisihurake-0 | 2022-05-12 | Code ▽ | License: CC4-BY-NC-SA
1

• 1st, the diagonal movement seems too slow.
• 2nd, after a few try, it may be got stuck into the solid block.

English is not my tongue, I hope my English get through to you.

Thank you.

P#111730 2022-05-12 14:22

:: merwok
2

I normalize diagonal speed thanks to this very good guide: https://demoman.net/?a=normalizing-input

 ``` local dx,dy=0,0 if (btn(⬅️)) dx=-player.spd if (btn(➡️)) dx=player.spd if (btn(⬆️)) dy=-player.spd if (btn(⬇️)) dy=player.spd if dx*dy!=0 then dx*=.7071 dy*=.7071 end```

Your division by √2 is the same as multiplication by 0.7071, so it should give the same result.
Maybe it’s some of the other code that causes the unwanted effect?

As a player, the diagonal movement in your game seems like a direct effect of my inputs, whereas straight moves feel that they go too far.

P#111734 2022-05-12 14:45 ( Edited 2022-05-12 14:52)
1

The reason the diagonal movement is slow is because you applied the diagonal correction too broadly. The result is that the correction ramps up the friction. I don't know how to explain what's needed in simple words, so here's the code changes in their entirety:

Remove this part in the code:

 ``` -- correct diagonal speed if (obj.dx * obj.dy) ~= 0 then obj.dx = obj.dx / sqrt(2) obj.dy = obj.dy / sqrt(2) end```

Then change this part

 ``` if btn(0) then p.dx-=p.acc end if btn(1) then p.dx+=p.acc end if btn(2) then p.dy-=p.acc end if btn(3) then p.dy+=p.acc end -- limit speed p.dx=mid(-p.maxd,p.dx,p.maxd) p.dy=mid(-p.maxd,p.dy,p.maxd)```

to this

 ``` local accx,accy,maxd=0,0,p.maxd if btn(0) then accx-=p.acc end if btn(1) then accx+=p.acc end if btn(2) then accy-=p.acc end if btn(3) then accy+=p.acc end -- correct for diagonals if accx*accy~=0 then accx=accx/sqrt(2) accy=accy/sqrt(2) maxd=maxd/sqrt(2) end p.dx+=accx p.dy+=accy -- limit speed p.dx=mid(-maxd,p.dx,maxd) p.dy=mid(-maxd,p.dy,maxd)```

That does about the same thing as merwok's suggestion, but it does it to the specific vectors in use rather than the overall situation. That said, the movement won't look smooth because of the lack of sub-pixel drawing and also because your movement is implicitly rounded by your apply_move() function.

Regarding getting stuck sometimes, that one's harder to explain or fix. The problem is really subtle.

Here's the typical case:
-- player need to move 1.4 pixels to the left.
-- code finds that 1.4 is more than 0.
-- code finds that spot 0 pixels to the left doesn't have a wall
-- code finds that 1.4 is more than 1.
-- code finds that spot 1 pixel to the left doesn't have a wall
-- code finds that 1.4 is less than 2.
-- code moves player to the left 2.

The problem occurs when the wall is a fraction number of pixels away from where the player would theoretically end up. In that case, the code misses the part that checks for the wall in the last run of the loop.

The easiest way I can think of to fix it is to raise v until it hits a solid or is too high and then always move it back.

Replace this code

 ``` if obj.dx > 0 then if not is_solid(obj, "right", 1) then local rv = 0 while rv < obj.dx do if is_solid(obj, "right", rv) then obj.dx = 0 rv = rv - 1 break end rv = rv + 1 end obj.x = obj.x + rv end end```

with this

 ``` if obj.dx > 0 then if not is_solid(obj, "right", 1) then local rv = 0 while rv < obj.dx and not is_solid(obj,"right",rv) do rv = rv + 1 end if is_solid(obj, "right", rv) then obj.dx = 0 rv = rv - 1 end obj.x = obj.x + rv end end```

Then, do the same for the other directions.

P#111745 2022-05-12 18:19
1

Out of curiosity, I wanted to implement my own "is_solid"-function to this which might be useful.
https://www.lexaloffle.com/bbs/?tid=46181

I did the following:

• Replaced the "is_solid"-function with my own.

 ```function is_solid(opt,f,ox,oy,flags,debug) local collist={} ox = ox or 0 oy = oy or 0 flags=flags or {0} if(type(flags)!="table")flags={flags} local ix=f.x+f.ox+ox local iy=f.y+f.oy+oy for x=ix,ix+f.w+7,8 do for y=iy,iy+f.h+7,8 do if opt==nil or opt=="full" or opt=="left" and x==ix or opt=="right" and x>=ix+f.w or opt=="up" and y==iy or opt=="down" and y>=iy+f.h or opt=="rdown" and y>=iy+f.h and x>=ix+f.w or opt=="ldown" and x==ix and y>=iy+f.h or opt=="rup" and y==iy and x>=ix+f.w or opt=="lup" and y==iy and x==ix then add(collist, {x=min(x,ix+f.w), y=min(y,iy+f.h)}) end end end if(debug)print(#collist) for c in all(collist) do if(debug)pset(c.x,c.y,8) for v in all(flags) do if(fget(mget(c.x/8,c.y/8),v))return true end end return false end```

• Added and modified the Player-Table, like:
 ```p = { w=7, -- modified h=7, -- modified ox=0, -- added oy=0, -- added [...] }```
• Added a Function that allows reducing Variables to zero, like:
 ```function step_to_zero(ret,value) if(ret>0)return max(0,ret-value) return min(0,ret+value) end```
• After that, I replaced the "apply_move"-function with this:
 ```function apply_move(obj) local dx=obj.dx local dy=obj.dy while is_solid("full",obj,dx) do dx=step_to_zero(dx,1) end obj.x+=dx while is_solid("full",obj,0,dy) do dy=step_to_zero(dy,1) end obj.y+=dy end```

The "step_to_zero" is needed because the Player is able to move more than one Pixel per Frame. If collision happens, then the code tries to reduce the "Speed" (dx or dy) of the Player to 1 Pixel and checks again, until there's basically no collision possible (because dx/dy are set to zero, which have to be collision-free because the Player's already on that Position...). I have to admit that this is a bit dirty, because it doesn't smooth out exacly 100% of the Values (everything that's between .0001 and .9999 are missing), but at least it's enough to "visually connect" to the Collision...

P#111756 2022-05-13 00:49

Thank you, merwok, kimiyoribaka, Astorek86 !!!
Thanks to your great advice, I will be able to proceed with my development.
Someday I would like to show you the finished version.
Thank you.

P#111785 2022-05-13 13:20

Hi guys.