Log In  

The Question

Hello everyone,

I am ludicrously close to finishing the engine for my game, but some physics equations have me stuck. Here is what I'm trying to achieve:

  • The players are effectively nodes that can switch between being pendulum bobs and fulcrums. One can be anchored to serve as a fulcrum while the other swings. If both are anchored, neither moves. If both try to un-anchor themselves, they fall.
  • In order to let the players navigate the playfield, I want the swing of a pendulum bob to increase bit by bit. (This will let players climb the playfield by swinging upward instead of just descending.)
  • Once a bob makes a full loop around the fulcrum, I want it to continue in a circle at a more-or-less consistent speed.

With the help of this math writeup, I managed to assemble a simplified set of physics variables and equations that do an okay job of simulating momentum and creating a non-degrading swing (i.e., the swing distance never shrinks due to friction). But I haven't figured out how to make the desired increase in swing distance happen. I'm sure it's a single value or sign that I haven't identified.

I suspect I need to do something to increment the value of p.a in the function move_player(p), but I am not sure how to approach it -- adding a value, multiplying by a value, etc.

Could someone better at physics/math please offer suggestions?

Thank you for any and all pointers!

The annotated code is below. It's 200 lines in total, but the relevant parts are the first ~90.

Cart #zafebaraza-0 | 2024-06-14 | Code ▽ | Embed ▽ | No License
1

The Code

function _init()

 screen_center=63

 player_size=5
 tether_length=25
 g=20 --gravity/momentum. more=stronger pull. negative values flip the swing's arc.
 k=-(g/tether_length)
 default_v=0 --negative=swing left, positive=swing right
 default_dt=0.01 --period/speed of swing (screwy if >=1)

 dt_cap=0.1
 dt_boost=dt_cap/(g*10)

 red={
  controller=0,
  button=5,
  control_mode="toggle", --"hold" or "toggle"
  x=screen_center-tether_length/2,
  y=screen_center,
  colors={2,8,2},
  r=player_size,
  a=0,
  v=default_v,
  dt=default_dt,
  glyph=nil,
  anchor=nil
  }

 blue={
  controller=0,
  button=4,
  control_mode="toggle",
  x=screen_center+tether_length/2,
  y=screen_center,
  colors={1,12,1},
  r=player_size,
  a=0,
  v=default_v,
  dt=default_dt,
  glyph=nil,
  anchor=nil
  }

 red.partner=blue
 blue.partner=red

 players={red,blue}

 for p in all(players) do
  set_glyph(p)
 end

end
-->8
--updates

function _update()
 for p in all(players) do
  control_player(p)
  check_angle(p)
  move_player(p)
 end
end

function move_player(p)

 if p.anchor==false and p.partner.anchor==true then

  p.v+=k*cos(p.a)*p.dt

  --to do: steadily increase swing length
  --to do: circular movement at consistent speed after looping
  --what i really want to do is boost dt and angle at the end of each swing
  p.a+=p.v*p.dt

  --dt should start small, increase, and cap around 0.1
  p.dt+=dt_boost
  if p.dt>dt_cap then
   p.dt=dt_cap
  end

  p.x=p.partner.x+tether_length*cos(p.a)
  p.y=p.partner.y+tether_length*sin(p.a)

 end

 if p.anchor==false and p.partner.anchor==false then
  --to-do: let swinging players finish their swing
  p.y+=g/5
 end

end

--everything below this line works, and is included in case anyone wants to paste the full code into a cart

function control_player(p)
 if p.control_mode=="toggle" then
  if p.anchor==nil then
   p.anchor=true
  end
  if btnp(p.button,p.controller) then
   if not out_of_bounds(p) then
    p.anchor=not p.anchor
    p.dt=default_dt
    p.v=default_v
   end
  end
  if p.anchor==true then
   p.colors[3]=7
  else
   p.colors[3]=p.colors[1]
  end
 elseif p.control_mode=="hold" then
  if p.anchor==nil then
   p.anchor=false
  end 
  if btn(p.button,p.controller) then
   if not out_of_bounds(p) then
    p.anchor=true
   end
  else
   p.anchor=false
  end
  if p.anchor==true then
   p.dt=default_dt
   p.v=default_v
   p.colors[3]=7
  elseif p.anchor==false then
   p.colors[3]=p.colors[1]
  end
 end
end

function check_angle(p)
 local angle=atan2(p.x-p.partner.x,p.y-p.partner.y)
 p.a=angle
end

function out_of_bounds(p)
 if p.x>128 or p.x<0 or p.y>128 or p.y<0 then
  return true
 end
end

function set_glyph(p)
 local glyphs={"⬅️","➡️","⬆️","⬇️","🅾️","❎"}
 p.glyph=glyphs[p.button+1]
end
-->8
--draws

function _draw()
 cls()
 draw_debug(red,1)
 draw_debug(blue,85)
 draw_tether(red,blue)
 for p in all(players) do
  draw_player(p)
 end
end

function draw_player(p)
 circfill(p.x,p.y,p.r,p.colors[1])
 circfill(p.x,p.y,p.r-1,p.colors[2])
 circfill(p.x,p.y,p.r-3,p.colors[3])
 print(p.glyph,p.x-3,p.y-2,p.colors[2])
 for i=1,2 do
  pset(p.x+1+i,p.y-5+i,7)
 end
end

function draw_tether(p1,p2)
 line(p1.x,p1.y,p2.x,p2.y,10)
end

function draw_debug(p,spot)
 local bottom=123
 local col=p.colors[2]
 if p.anchor==true then
  print("anchored",spot,bottom-21,col)
 else
  print("unanchored",spot,bottom-21,col)
 end
 print("v: "..p.v,spot,bottom-14,col)
 print("a: "..p.a,spot,bottom-7,col)
 print("dt: "..p.dt,spot,bottom,col)
end

P#149913 2024-06-14 03:05

2

Try this: don't change dt, and instead do something like:

  p.v+=(k*cos(p.a)+0.01*sgn(p.v))*p.dt

This adds a small angular acceleration in the direction of the current velocity, and should increase the angle of swing over time.

P#149918 2024-06-14 04:32

This works beautifully! I'd never heard of sgn() before, either, but I can see it being quite useful.

All I need to do now is figure out how to speed up the swing from here.

Thank you, @luchak. You've helped me solve the last major obstacle in my game. Now for all the stuff I actually know how to do!

P#149935 2024-06-14 14:09

I actually did end up keeping the dt updates, because they made for a more pleasing swing speed. With a simple acceleration cap included, everything works nicely.

Thanks again, Luchak!

Cart #fawanefida-0 | 2024-06-14 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

P#149937 2024-06-14 14:23

[Please log in to post a comment]