I really enjoyed playing with @Luatee’s Ragdoll surfer, and was pretty impressed by the Ragdoll specifically. So, I wondered if I could accomplish something similar with the built-in physics.
After a bit of fiddling, I’ve got something that I think is fairly realistic.
Video:
http://www.youtube.com/watch?v=Rzmp75Blv-E
EDIT: I’m not sure why the colors are all wonky in the video, YouTube must have added some kind of filter when I uploaded it. Sorry about that.
EDIT 2: I think I’ve fixed that issue.
Code:
--# Main
-- Ragdoll Physics
function setup()
DebugDraw = PhysicsDebugDraw()
guy = Ragdoll(WIDTH / 2, HEIGHT / 2)
floor = physics.body(EDGE, vec2(0,0), vec2(WIDTH,0))
end
function draw()
background(40, 40, 50)
DebugDraw:draw()
end
function touched(t)
DebugDraw:touched(t)
end
--# Ragdoll
Ragdoll = class()
local bodyBox = function(width, height, x, y)
local box = physics.body(POLYGON, vec2(-width / 2, -height / 2), vec2(width / 2, -height / 2), vec2(width / 2, height / 2), vec2(-width / 2, height / 2))
box.restitution = 0.1
box.friction = 0.4
box.position = vec2(x, y)
return box
end
local bodyJoint = function(lower, upper, b1, b2, x, y)
local joint = physics.joint(REVOLUTE, b1, b2, vec2(x, y))
joint.enableLimit = true
joint.lowerLimit = lower
joint.upperLimit = upper
return joint
end
function Ragdoll:init(x, y)
self.bones, self.joints = {}, {}
-- Sizes
local headSize = 50
local torsoWidth, torsoHeight = 30, 20
local upperArmWidth, upperArmHeight = 13, 36
local lowerArmWidth, lowerArmHeight = 12, 34
local upperLegWidth, upperLegHeight = 15, 44
local lowerLegWidth, lowerLegHeight = 12, 40
-- Bones
-- Head
local head = physics.body(CIRCLE, headSize / 2)
head.restitution = 0.3
head.friction = 0.4
head.position = vec2(x, y)
self.bones[1] = head
-- Torso
self.bones[2] = bodyBox(torsoWidth, torsoHeight, x, y - headSize / 2 - torsoHeight / 2)
self.bones[3] = bodyBox(torsoWidth, torsoHeight, x, y - headSize / 2 - torsoHeight * 1.5)
self.bones[4] = bodyBox(torsoWidth, torsoHeight, x, y - headSize / 2 - torsoHeight * 2.5)
-- Arms
self.bones[5] = bodyBox(upperArmWidth, upperArmHeight, x - torsoWidth / 2 - upperArmWidth / 2, y - headSize / 2 - upperArmHeight / 2)
self.bones[6] = bodyBox(upperArmWidth, upperArmHeight, x + torsoWidth / 2 + upperArmWidth / 2, y - headSize / 2 - upperArmHeight / 2)
self.bones[7] = bodyBox(lowerArmWidth, lowerArmHeight, x - torsoWidth / 2 - upperArmWidth / 2, y - headSize / 2 - upperArmHeight - lowerArmHeight / 2)
self.bones[8] = bodyBox(lowerArmWidth, lowerArmHeight, x + torsoWidth / 2 + upperArmWidth / 2, y - headSize / 2 - upperArmHeight - lowerArmHeight / 2)
-- Legs
self.bones[9] = bodyBox(upperLegWidth, upperLegHeight, x - upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3 - upperLegHeight / 2)
self.bones[10] = bodyBox(upperLegWidth, upperLegHeight, x + upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3 - upperLegHeight / 2)
self.bones[11] = bodyBox(lowerLegWidth, lowerLegHeight, x - upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3 - upperLegHeight - lowerLegHeight / 2)
self.bones[12] = bodyBox(lowerLegWidth, lowerLegHeight, x + upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3 - upperLegHeight - lowerLegHeight / 2)
-- Joints
-- Head to torso
self.joints[1] = bodyJoint(-40, 40, self.bones[2], self.bones[1], x, y - headSize / 2)
-- Upper arms to torso
self.joints[2] = bodyJoint(-85, 130, self.bones[2], self.bones[5], x - torsoWidth / 2, y - headSize / 2)
self.joints[3] = bodyJoint(-130, 85, self.bones[2], self.bones[6], x + torsoWidth / 2, y - headSize / 2)
-- Upper arms to lower arms
self.joints[4] = bodyJoint(-130, 10, self.bones[5], self.bones[7], x - torsoWidth / 2 - upperArmWidth / 2, y - headSize / 2 - upperArmHeight)
self.joints[5] = bodyJoint(-10, 130, self.bones[6], self.bones[8], x + torsoWidth / 2 + upperArmWidth / 2, y - headSize / 2 - upperArmHeight)
-- Torsos (Shoulders -> Stomach -> Hips)
self.joints[6] = bodyJoint(-15, 15, self.bones[2], self.bones[3], x, y - headSize / 2 - torsoHeight)
self.joints[7] = bodyJoint(-15, 15, self.bones[3], self.bones[4], x, y - headSize / 2 - torsoHeight * 2)
-- Hips to upper legs
self.joints[8] = bodyJoint(-25, 45, self.bones[4], self.bones[9], x - upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3)
self.joints[9] = bodyJoint(-45, 25, self.bones[4], self.bones[10], x + upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3)
-- Upper legs to lower legs
self.joints[10] = bodyJoint(-25, 115, self.bones[9], self.bones[11], x - upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3 - upperLegHeight)
self.joints[11] = bodyJoint(-115, 25, self.bones[10], self.bones[12], x + upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3 - upperLegHeight)
for i, bone in ipairs(self.bones) do
DebugDraw:addBody(bone)
end
for i, joint in ipairs(self.joints) do
DebugDraw:addJoint(joint)
end
end
--# PhysicsDebugDraw
PhysicsDebugDraw = class()
function PhysicsDebugDraw:init()
self.bodies = {}
self.joints = {}
self.touchMap = {}
self.contacts = {}
end
function PhysicsDebugDraw:addBody(body)
table.insert(self.bodies,body)
end
function PhysicsDebugDraw:addJoint(joint)
table.insert(self.joints,joint)
end
function PhysicsDebugDraw:clear()
-- deactivate all bodies
for i,body in ipairs(self.bodies) do
body:destroy()
end
for i,joint in ipairs(self.joints) do
joint:destroy()
end
self.bodies = {}
self.joints = {}
self.contacts = {}
self.touchMap = {}
end
function PhysicsDebugDraw:draw()
pushStyle()
smooth()
strokeWidth(5)
stroke(128,0,128)
local gain = 2.0
local damp = 0.5
for k,v in pairs(self.touchMap) do
local worldAnchor = v.body:getWorldPoint(v.anchor)
local touchPoint = v.tp
local diff = touchPoint - worldAnchor
local vel = v.body:getLinearVelocityFromWorldPoint(worldAnchor)
v.body:applyForce( (1/1) * diff * gain - vel * damp, worldAnchor)
line(touchPoint.x, touchPoint.y, worldAnchor.x, worldAnchor.y)
end
stroke(0,255,0,255)
strokeWidth(5)
for k,joint in pairs(self.joints) do
local a = joint.anchorA
local b = joint.anchorB
line(a.x,a.y,b.x,b.y)
end
stroke(255,255,255,255)
noFill()
for i,body in ipairs(self.bodies) do
pushMatrix()
translate(body.x, body.y)
rotate(body.angle)
if body.type == STATIC then
stroke(255,255,255,255)
elseif body.type == DYNAMIC then
stroke(150,255,150,255)
elseif body.type == KINEMATIC then
stroke(150,150,255,255)
end
if body.shapeType == POLYGON then
strokeWidth(3.0)
local points = body.points
for j = 1,#points do
a = points[j]
b = points[(j % #points)+1]
line(a.x, a.y, b.x, b.y)
end
elseif body.shapeType == CHAIN or body.shapeType == EDGE then
strokeWidth(3.0)
local points = body.points
for j = 1,#points-1 do
a = points[j]
b = points[j+1]
line(a.x, a.y, b.x, b.y)
end
elseif body.shapeType == CIRCLE then
strokeWidth(3.0)
line(0,0,body.radius-3,0)
ellipse(0,0,body.radius*2)
end
popMatrix()
end
stroke(255, 0, 0, 255)
fill(255, 0, 0, 255)
for k,v in pairs(self.contacts) do
for m,n in ipairs(v.points) do
ellipse(n.x, n.y, 10, 10)
end
end
popStyle()
end
function PhysicsDebugDraw:touched(touch)
local touchPoint = vec2(touch.x, touch.y)
if touch.state == BEGAN then
for i,body in ipairs(self.bodies) do
if body.type == DYNAMIC and body:testPoint(touchPoint) then
self.touchMap[touch.id] = {tp = touchPoint, body = body, anchor = body:getLocalPoint(touchPoint)}
return true
end
end
elseif touch.state == MOVING and self.touchMap[touch.id] then
self.touchMap[touch.id].tp = touchPoint
return true
elseif touch.state == ENDED and self.touchMap[touch.id] then
self.touchMap[touch.id] = nil
return true;
end
return false
end
function PhysicsDebugDraw:collide(contact)
if contact.state == BEGAN then
self.contacts[contact.id] = contact
sound(SOUND_HIT, 2643)
elseif contact.state == MOVING then
self.contacts[contact.id] = contact
elseif contact.state == ENDED then
self.contacts[contact.id] = nil
end
end
The code borrows the PhysicsDebugDraw class from the physics example because I didn’t want to waste time drawing and touching the bodies when I could just use this existing code.