|
 |
« Reply #3 on: May 21, 2014, 03:43:45 AM » |
|
How far have you guys gotten on new Actions? I stopped working on a brawl-like game recently but I based the moveset system off PSA actions/subactions from the PSA docs. Here's what I learned and implemented that might be useful to others: Actions - run every game step - nothing special, more like an animation finite state machine - those "ChangeAction" events are the transitions between states/actions - in Unity, it's represented similarly. -->the animation system has a bag of variables that are used to decide when wand what to transition to if a requirement succeeds (--> ChangeActions) - Early on, I noticed that Actions never(?) contain any timer events (Asynchronous, Synchronous timers), but made plenty use of infinite loops + loop rests. -->this backs up the idea that Actions are ran once per game step, -->anyone who uses timers in there are probably misusing the event and are pausing the action for an entire animation frame instead of a single game step (loop rest) -Since actions are just FSMs, you can create an entire alternative moveset if you can add actions. --within memory limits ofc. -gives control of an Action at the per game step Subaction - run once per animation step, sets an animation - really, just gives control of an Action/animation code on a per animation frame step. (very useful as everyone who PSA's should know by now) - I used to have an "AllowInterrupt" "event" in my game too. Straight to the point, "AllowInterrupt" (do you guys still name it that?) is just a fancy word for "specific change actions". --All current ChangeActions on my side are cached and checked at the end of a game step. --when an Action changes, any currently existing ChangeActions events are thrown away -Again, Actions are nothing special but a FSM -but at the PSA-like level, they're very easy to use (for those who program, a FSM doesn't scale well in code. However, the way Brawl does it for Actions, they scale fine.) -Subactions also aren't anything special but a small script uhh if I think of anything else that might be useful, I'll be sure to add to this. These are just things I learned about when I was using PSA docs for guidance on how to setup a PSA-like system. AFAIK, both systems turned out similar. This is pretty much advertising a game I don't work on anymore... but I dunno it might be useful to see how my side turned out? *Note*, anywhere you see coroutine.yield(runStatus.Running) within an infinite loop, it's similar to a LoopRest in PSA *WARNING* raw and uneditted. This is a debug/test moveset using Lua Actions local mitActions = function(_events) local events = _events
events.lightPunchKeyPress = function() return events:isKeyPress(keys.X) end events.jumpKeyPressed = function() return events:isKeyPressed(keys.Z) end events.crouchKeyPressed = function() return events:isKeyPressed(keys.Down) end events.upKeyPressed = function() return events:isKeyPressed(keys.Up) end events.heavy_UpKeyPressed = function() return events:isKeyPressed(keys.Up) and events:isKeyPressed(keys.C) end events.horizontalKeysPressed = function() return events:areKeysPressed(events.horizontalKeys) end events.keyPressedTowardsWall = function() local wallSide = events:getWallSide() return (events:isKeyPressed(keys.Left) and wallSide == -1) or (events:isKeyPressed(keys.Right) and wallSide == 1) end --add change actions for: attack11,jumpBegin,crouch,walk,heavyUpAttackBegin events.changeActions_Ground0 = function() events:changeAction(events.actions.walk,events.horizontalKeysPressed) events:addRequirement(events.onGround) events:addRequirement(events.shouldBeOnGround) events:changeAction(events.actions.jumpBegin,events.jumpkeyPressed) events:addRequirement(events.onGround) events:addRequirement(events.shouldBeOnGround) events:changeAction(events.actions.crouch,events.crouchKeyPressed) events:addRequirement(events.onGround) events:addRequirement(events.shouldBeOnGround) events:changeAction(events.actions.attack11,events.lightPunchKeyPress) events:addRequirement(events.onGround) events:addRequirement(events.shouldBeOnGround) end
--add change actions for: airN,airF,wallSlide,hammerUp events.changeActions_Air0 = function() events:changeAction(events.actions.walk,events.horizontalKeysPressed) events:changeAction(events.actions.walk,events.horizontalKeysPressed) events:changeAction(events.actions.walk,events.horizontalKeysPressed) events:changeAction(events.actions.walk,events.horizontalKeysPressed) end --empty func for now,replace with specifics later. events.allowInterrupt = function()end events.actions = { idle = action( { id = 0, method = function() --yield once to stay in sync with how game handles coroutines internally coroutine.yield() events:changeAction(events.actions.walk,events.horizontalKeysPressed) events:addRequirement(events.onGround) events:addRequirement(events.shouldBeOnGround) events:changeAction(events.actions.jumpBegin,events.jumpKeyPressed) events:addRequirement(events.onGround) events:addRequirement(events.shouldBeOnGround) events:changeAction(events.actions.attack11,events.lightPunchKeyPress) events:addRequirement(events.onGround) events:addRequirement(events.shouldBeOnGround)
events:changeAction(events.actions.heavyAttack_Up_Begin,events.heavy_UpKeyPressed)
events:changeAction(events.actions.fall,events.isFalling) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir)
events:setSubaction(events.subactions.idle) while true do coroutine.yield(runStatus.Running) end end }), walk = action( { id = 1, method = function() coroutine.yield() events:changeAction(events.actions.idle,events.horizontalKeysPressed,true) events:changeAction(events.actions.fall,events.isFalling) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir)
events:changeAction(events.actions.jumpBegin,events.jumpKeyPressed) events:addRequirement(events.onGround)
events:changeAction(events.actions.attack11,events.lightPunchKeyPress) events:addRequirement(events.onGround) events:addRequirement(events.shouldBeOnGround) events:changeAction(events.actions.heavyAttack_Up_Begin,events.heavy_UpKeyPressed)
events:setSubaction(events.subactions.walk) while true do events:moveRelative() coroutine.yield(runStatus.Running) end end }), jumpBegin = action( { id = 5, method = function() coroutine.yield() events:offsetRelative(vector2(0,-3)) events:applyJumpImpulse() events:setGroundAirTime(0) --events:ignoreGroundCollisions() --events:playSound(3,0.5)
events:setSubaction(events.subactions.jumpBegin) coroutine.yield(runStatus.Running) events:setShouldBeInAir(true) events:setInAir(true) events:changeAction(events.actions.fall,events.isFalling) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir)
events:changeAction(events.actions.rise,events.isRising) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir)
while true do coroutine.yield(runStatus.Running) end end }), rise = action( { id = 6, method = function() coroutine.yield() events:changeAction(events.actions.idle,events.onGround) events:addRequirement(events.shouldBeOnGround)
events:changeAction(events.actions.fall,events.isFalling) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir)
events:changeAction(events.actions.airN,events.lightPunchKeyPress) events:addRequirement(events.horizontalKeysPressed,true) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir)
events:changeAction(events.actions.airF,events.lightPunchKeyPress) events:addRequirement(events.isKeyPressed_Forward) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir) events:changeAction(events.actions.heavyAttack_Up_Begin,events.heavy_UpKeyPressed)
events:setSubaction(events.subactions.jumpLoop)
while true do events:move()
--try to rise if(events:isRiseTimeLessThanAllowed() and events:isKeyPressed(events.jumpKeys)) then events:setVelocityY(-150) end
coroutine.yield(runStatus.Running) end end }), fall = action( { id = 7, method = function() coroutine.yield() events:changeAction(events.actions.idle,events.onGround) events:addRequirement(events.shouldBeOnGround)
events:changeAction(events.actions.rise,events.isRising) events:addRequirement(events.inAir)
events:changeAction(events.actions.wallSlide,events.isFalling) events:addRequirement(events.isTouchingWall) events:addRequirement(events.keyPressedTowardsWall)
events:changeAction(events.actions.airN,events.lightPunchKeyPress) events:addRequirement(events.horizontalKeysPressed,true) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir)
events:changeAction(events.actions.airF,events.lightPunchKeyPress) events:addRequirement(events.isKeyPressed_Forward) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir)
events:changeAction(events.actions.heavyAttack_Up_Begin,events.heavy_UpKeyPressed)
events:setSubaction(events.subactions.fall)
while true do events:move() coroutine.yield(runStatus.Running) end end }), attack11 = action( { id = 10, method = function() coroutine.yield()
events:changeAction(events.actions.idle,events.isSubactionNull) events:setSubaction(events.subactions.attack11)
while true do coroutine.yield(runStatus.Running) end end }), attack12 = action( { id = 11, method = function() coroutine.yield() events:setSubaction(events.subactions.attack12)
while true do coroutine.yield(runStatus.Running) end end }), attack13 = action( { id = 12, method = function() coroutine.yield() events:setSubaction(events.subactions.attack13) while true do coroutine.yield(runStatus.Running) end end }), heavyAttack_Idle = action( { id = 13, method = function() coroutine.yield() events:setSubaction(events.subactions.heavyAttack_Idle) while true do coroutine.yield(runStatus.Running) end end }), heavyAttack_Up_Begin = action( { id = 14, method = function() coroutine.yield()
events:setSubaction(events.subactions.heavyAttack_Up_Begin) events:changeAction(events.actions.heavyAttack_Up_Loop,events.isSubactionNull)
while true do coroutine.yield(runStatus.Running) end end }), heavyAttack_Up_Loop = action( { id = 15, method = function() coroutine.yield() events:setSubaction(events.subactions.heavyAttack_Up_Loop) events:changeAction(events.actions.heavyAttack_Up_End,events.upKeyPressed,true) while true do events:moveRelative()
coroutine.yield(runStatus.Running) end end }), heavyAttack_Up_End = action( { id = 16, method = function() coroutine.yield() events:setSubaction(events.subactions.heavyAttack_Up_End) events:changeAction(events.actions.idle,events.isSubactionNull)
while true do events:moveRelative() coroutine.yield(runStatus.Running) end end }), heavyAttack_AirN = action( { id = 20, method = function() coroutine.yield() events:setSubaction(events.subactions.heavyAttack_AirN) while true do coroutine.yield(runStatus.Running) end end }), airN = action( { id = 21, method = function() coroutine.yield() events:changeAction(events.actions.airNFall,events.isSubactionNull)
events:setSubaction(events.subactions.airN) while true do coroutine.yield(runStatus.Running) end end }), airNFall = action( { id = 22, method = function() coroutine.yield() events:changeAction(events.actions.idle,events.onGround) events:addRequirement(events.shouldBeOnGround)
events:changeAction(events.actions.rise,events.isRising) events:addRequirement(events.inAir)
events:changeAction(events.actions.wallSlide,events.isFalling) events:addRequirement(events.isTouchingWall) events:addRequirement(events.keyPressedTowardsWall)
events:changeAction(events.actions.airN,events.lightPunchKeyPress) events:addRequirement(events.horizontalKeysPressed,true) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir)
events:changeAction(events.actions.airF,events.lightPunchKeyPress) events:addRequirement(events.isKeyPressed_Forward) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir)
events:setSubaction(events.subactions.airNFall) while true do events:move() coroutine.yield(runStatus.Running) end end }), airF = action( { id = 23, method = function() coroutine.yield() events:changeAction(events.actions.fall,events.isSubactionNull) events:setSubaction(events.subactions.airF) while true do coroutine.yield(runStatus.Running) end end }), wallSlide = action( { id = 30, method = function() coroutine.yield() events:changeAction(events.actions.idle,events.onGround) events:addRequirement(events.shouldBeOnGround)
events:changeAction(events.actions.fall,events.isFalling) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir) events:addRequirement(events.keyPressedTowardsWall,true)
events:changeAction(events.actions.wallJump,events.jumpKeyPressed) events:setSubaction(events.subactions.wallSlide) while true do events:setVelocity(vector2(0,15)) coroutine.yield(runStatus.Running) end end }), wallJump = action( { id = 31, method = function() coroutine.yield() events:setSubaction(events.subactions.wallJump) coroutine.yield(runStatus.Running) events:changeAction(events.actions.fall,events.isFalling) events:addRequirement(events.inAir) events:addRequirement(events.shouldBeInAir) while true do --events:move() --try to rise --if(events:isRiseTimeLessThanAllowed() and -- events:isKeyPressed(events.jumpKeys)) then -- events:setVelocityY(-150) --end
coroutine.yield(runStatus.Running) end end }), }
end mitActions(...) Subactions local create = function(_events) local events = _events
--to prevent creating strings everytime used local bones = { HandL = 'HandL', HandR = 'HandR', FootL = 'FootL', FootR = 'FootR', Hammer = 'Hammer' } events.subactions = { idle = subaction( { id = 0, animationID = 0, looped = true, functions = { main = function() -- first yield gets consumed by coroutine start coroutine.yield() end } }), walk = subaction( { id = 1, animationID = 1, looped = true, functions = { main = function() coroutine.yield() events:allowInterrupt() events:playSound(0,0.1) events:synchronous(12) events:playSound(0,0.1) events:synchronous(11) end } }), jumpBegin = subaction( { id = 5, animationID = 2, looped = false, functions = { main = function() coroutine.yield() events:addParticleRelative(particle(13,1,color.White,vector2(0,10),vector2(-20,0),false,true)) events:addParticleRelative(particle(13,1,color.White,vector2(0,10),vector2(20,0),false, true)) events:allowInterrupt() end } }), jumpLoop = subaction( { id = 6, animationID = 2, looped = true, functions = { main = function() coroutine.yield() events:allowInterrupt() end } }), fall = subaction( { id = 7, animationID = 3, looped = true, functions = { main = function() coroutine.yield() events:allowInterrupt() end } }), attack11 =subaction( { id = 10, animationID = 9, looped = false, functions = { main = function() coroutine.yield() events:synchronous(4) events:playSound(5,1) events:offensiveCollision(0, true, bones.HandL, 1, 2, 180,0, 3, 4, vector2(-1, 0), 3, 1,0,5) events:offensiveCollision(1, true, bones.HandL, 1, 2, 135,0, 4, 4, vector2(-1, 0), 8, .2,0,5) events:offensiveCollision(2, true, bones.HandL, 1, 2, 0,0, 1, 1, vector2(-1, 0), 10, 0,0,5) events:asynchronous(5) for i=0,10 do events:synchronous(1) if(events:isKeyPress(events.punchKeys)) then events:terminateAllCollisions() events:setSubaction(events.subactions.attack12) end end events:asynchronous(16) events:terminateAllCollisions() end } }), attack12 =subaction( { id = 11, animationID = 10, looped = false, functions = { main = function() coroutine.yield()
events:asynchronous(4) events:playSound(6,1) events:synchronous(1) events:offensiveCollision( 0, true, bones.HandR, 1, 2, 270,0, 5, 5,vector2(-1, 0), 10, 1) for i = 0,10 do events:synchronous(1) if(events:isKeyPress(events.punchKeys)) then events:terminateAllCollisions() events:setSubaction(events.subactions.attack13) return coroutine.yield(runStatus.Success) end end
events:asynchronous(16) events:terminateAllCollisions() end } }), attack13 =subaction( { id = 12, animationID = 11, looped = false, functions = { main =function() coroutine.yield()
events:asynchronous(4) events:playSound(7) events:offensiveCollision(0, true, bones.FootR, 1, 2, 45,0, 4, 10,vector2(-1, 0), 10, 2) events:asynchronous(16) events:terminateAllCollisions() end } }), heavyAttack_Idle =subaction( { id = 13, animationID = 4, looped = false, functions = { main = function() coroutine.yield() events:playSound(3,1) end } }), heavyAttack_Up_Begin =subaction( { id = 14, animationID = 13, looped = false, functions = { main = function() coroutine.yield() events:playSound(28,.1) events:asynchronous(15) if(events:inAir()) then events:playSound(15,.2) events:setVelocityY(-200) end end, gfx = function() coroutine.yield() --BIFF local _particle =particle(32,.5,color.White) _particle:SetInitialMatrix(-15,-25,0,.5,.5,0,0) _particle:SetPosition(0,10,0,-100) _particle:SetRotation(3.14/-2,3.14) _particle:SetScale(1.5,1,0,0) _particle.FadeOut=true events:addParticleRelative(_particle) events:asynchronous(15) --MARTY _particle.AnimationIndex = 34 _particle:SetInitialMatrix(15,-25,0,.5,.5,0,0) _particle:SetRotation(3.14 / 2,-3.14) events:addParticleRelative(_particle) if(events:inAir()) then
--smoke puff _particle.AnimationIndex = 11 _particle:SetInitialMatrix(0,5,0,.5,.5,0,0) _particle:SetPosition(0,20,0,-60) _particle:SetRotation(0,0) _particle:SetScale(3,3,-10,-10)
events:addParticleRelative(_particle); _particle.LifeTime = 1 --leaves for i=1,4 do _particle.AnimationIndex = i _particle:SetInitialMatrix(0,5,0,1,1,0,0) _particle:SetPosition(random.NextDouble(-30,30),random.NextDouble(-10,30),0,50) _particle:SetRotation(6.14,-6.14) _particle:SetScale(0,0,0,0) events:addParticleRelative(_particle) end end end } }), heavyAttack_Up_Loop =subaction( { id = 15, animationID = 14, looped = true, functions = { main = function() coroutine.yield() local offset = vector2(0,-1)
events:offensiveCollision(0, true, bones.Hammer, 0, 0, 135,0, 0, 10, offset, 9, 1) events:offensiveCollision(1, true, nil, 0, 0, 135,0, 0, 10,vector2.Zero,10,1)-- vector2(0,-5), 6, 1) events:synchronous(15) events:terminateAllCollisions() end, gfx = function() coroutine.yield() local _particle = particle(0,.5,color.White) _particle.LifeTime = .5 for i=0,2 do if(events:onGround()) then --smoke trail _particle.AnimationIndex = 12 _particle:SetInitialMatrix(0,5,0,.5,.5,0,0) _particle:SetScale(-.5,-.5,0,0)
events:addParticleRelative(_particle)
--V particles _particle.AnimationIndex = 4 _particle:SetInitialMatrix(0,10,0,.5,.5,0,0) _particle:SetPosition(-60,-60,0,0) _particle:SetScale(5,5,0,0) events:addParticleRelative(_particle)
_particle:SetPosition(60,-60,0,0) events:addParticleRelative(_particle) end events:synchronous(5) end end ,sfx = function() coroutine.yield() events:playSound(27,.1) if(events:onGround()) then events:playSound(20,.01) end end } }), heavyAttack_Up_End = subaction( { id = 16, animationID = 15, looped = false, functions = { main = function() coroutine.yield()
end } }), heavyAttack_AirN = subaction( { id = 20, animationID = 22, looped = false, functions = { main = function() coroutine.yield() events:playSound(10,1) for i = 0,18 do if(events:shouldBeOnGround()) then events:setSubactionPassTime(events.subactions.heavyAttack_Idle,events:getCurrentAnimationFrameTime()) return coroutine.yield(runStatus.Success) end events:synchronous(1) end end } }), airN = subaction( { id = 21, animationID = 18, looped = false, functions = { main = function() coroutine.yield()
local offset = vector2(0,0)
events:synchronous(4) --events:playSound(4,1) events:enableConstantMomentum() events:setConstantMomentum(vector2.Zero) events:asynchronous(9) events:offensiveCollision(0, true, bones.HandL, 0, 0, 135,0, 0, 10, offset, 10, 1) events:offensiveCollision(1, true, bones.HandR, 0, 0, 45,0, 0, 10, offset, 10, 1) events:offensiveCollision(2, true, bones.FootL, 0, 0, 225,0, 0, 10, offset, 10, 1) events:offensiveCollision(3, true, bones.FootR, 0, 0, 315,0, 0, 10, offset, 10, 1) events:synchronous(3) events:terminateAllCollisions() events:offensiveCollision(0, true, bones.HandL, 0, 0, 135,0, 0, 8, offset, 8, 1) events:offensiveCollision(1, true, bones.HandR, 0, 0, 45 ,0, 0, 8, offset, 8, 1) events:offensiveCollision(2, true, bones.FootL, 0, 0, 225,0, 0, 8, offset, 8, 1) events:offensiveCollision(3, true, bones.FootR, 0, 0, 315,0, 0, 8, offset, 8, 1) events:synchronous(15) events:terminateAllCollisions() events:synchronous(5) events:disableConstantMomentum() events:allowInterrupt() end } }), airNFall = subaction( { id = 22, animationID = 19, looped = false, functions = { main = function() coroutine.yield() end } }), airF =subaction( { id = 23, animationID = 17, looped = false, functions = { main = function() coroutine.yield()
events:asynchronous(4) events:applyImpulse(vector2(0,-50)) events:offensiveCollision( 0,true, bones.HandL, 1, 1, 0,0, 50, 8,vector2(0, -4), 10, 3) --events:playSound(5) events:asynchronous(13) events:terminateAllCollisions() events:allowInterrupt() end } }), wallSlide =subaction( { id = 30, animationID = 16, looped = true, functions = { main = function() coroutine.yield()
--I don't want this subaction being called every frame because it plays sound local rnd = random.NextDouble(0,10)
local _particle = nil if(rnd >= 5) then local life = random.NextDouble(.3,2) _particle = particle(11,life,color.White) _particle:SetInitialMatrix(10,0,0,.7,.7,0,0) _particle:SetPosition(0,-5,0,0) _particle:SetRotation(random.NextDouble(0,6),0) _particle:SetScale(-1/life,-1/life,0,0) _particle.DeathOnAnimationEnd = true _particle.FadeOut = true events:addParticleRelative(_particle) end
rnd = random.NextDouble(0,10) if(rnd >= 8) then local life = random.NextDouble(.3,2) _particle = particle(11,life,color.White) _particle:SetInitialMatrix(10,10,0,.7,.7,0,0) _particle:SetPosition(0,-10,0,0) _particle:SetRotation(random.NextDouble(0,6),0) _particle:SetScale(-1/life,-1/life,0,0) _particle.DeathOnAnimationEnd = true _particle.FadeOut = true events:addParticleRelative(_particle) end
events:allowInterrupt() end } }), wallJump =subaction( { id = 31, animationID = 2, looped = false, functions = { main = function() coroutine.yield()
--events:playSound(13,1) events:addParticleRelative(particle(11,1,color.White,vector2(10, 0),vector2(0,-20),true, true)) events:addParticleRelative(particle(11,1,color.White,vector2(10, 0),vector2(0, 20),true, true))
local wallNormal = events.context.PlacementData.BodySensorNormalCollection.AverageNormal local jumpDirection = vector2.Transform(wallNormal,matrix.CreateRotationZ(-mathHelper.PiOver4))
jumpDirection.X = jumpDirection.X * 300 jumpDirection.Y = jumpDirection.Y * 300 if(jumpDirection.Y > 0) then jumpDirection.Y = jumpDirection.Y * -1.0 end
local levelMask = events.context.BasicPhysicsData.LevelCollisionMask
if(events:waitForPermission()) then events:offsetRelative( wallNormal) levelMask:ApplyLinearImpulse(jumpDirection) end
events:setFacingDirection(events:getWallSide() * -1) end } }), } end
return create(...) You can check my dead devlog for gifs what this code actually does . If this is against the rules, I have no problem if this post is deleted or editted. ___ Oh yea, I never got to fully implementing articles. The way I would've done articles was to have articles also have it's own "Moveset" data (actions/subactions). Then the owner (Mario->fireball) controls the visibility/active/enabled of an article as well as set Actions directly. To me (who hasn't PSA-ed in years), it sounds like how brawl might also do it.
|