MaiTerra Posted January 6, 2015 Share Posted January 6, 2015 So, I'm partway through modding a character for Don't Starve Together and I'm using the Extended Sample Character template which has been great and all, but once I started on the animation artwork for the character I realized a slight problem. The character's design is fairly asymmetrical (hair on one side of the head is styled differently than on the other side for one thing), and I've noticed that from the looks of it, Don't Starve and Don't Starve Together just flip the sideview images of a character depending on whether they're facing left or right, rather than using different images for different sides. So, I'm basically just wondering if there's a way to have the game use different images for the character depending on which side they're facing instead of just flipping them? (And of course if so, how would I do that?) Hopefully my question makes sense! I don't know if I wrote in out in a confusing way or anything. Link to comment Share on other sites More sharing options...
BrotatoTips Posted January 7, 2015 Share Posted January 7, 2015 if anything, the most practical answer for now is to recreate all of the animations along with a perfectly custom character template that utilizes your asymmetrical design. well, this in turn is very complicated and tedious. also, you probably wouldnt want to do this and it isnt very practical. but this is one method you could take. another possibility is making two separate character templates and name them 1 and 2. and make a separate anim build for these and have it utilize these also. so when left, it would display the anim directed at 1, and when facing right, vice versa. but all this would be difficult, and may in turn, not be possible. i assume someone else has a better answer than I, but in the downtime, at least i gave you an interesting read. (this kinda stuff works with items and such, and it would take some coding, but its plausible). Link to comment Share on other sites More sharing options...
Renamon Posted January 7, 2015 Share Posted January 7, 2015 the engine only uses three set stages back, front and one side- sadly its not possible. Link to comment Share on other sites More sharing options...
Ryuushu Posted January 7, 2015 Share Posted January 7, 2015 @MaiTerra After playing around for a while, it's quite possible, no need to remake all animations from scratch. And it's rather easy. Notice the eye color. Anyways, here's how you do it:First, save your current Spriter project as mycharacter_build_right. The side view will be the side that'll show when your character is facing right.Copy-paste your project folder (Spriter project + images) to a new folder. Then in that new Spriter project modify the side view images to your liking. Don't worry about your character facing right. Save your spriter project as mycharacter_build_left.Let the autocompiler do its job and throw your new anims in your mod anim folder. In your character file add the new anim assets.Now comes the fun part; handling which anim build we should use. We want to use mycharacter_build_right when it's facing right, and mycharacter_build_left when it's facing left. So, when do we know the character has started facing a side? We could use the event "locomote". That event will be sent when the character moves. So, we just add a listener to that event and use a bit of trickery to figure out what direction is the character facing: inst:ListenForEvent("locomote", function(inst) if inst:HasTag("playerghost") then return end local dir = inst.Transform:GetRotation() local camera_rot = TheCamera:GetHeadingTarget() print("Camera rotation:", camera_rot) print("Direction:", dir) if camera_rot < 0 then camera_rot = camera_rot + 360 end if camera_rot == 0 or camera_rot == 45 then if dir <= 0 then inst.AnimState:SetBuild("esctemplate_right") else inst.AnimState:SetBuild("esctemplate_left") end elseif camera_rot == 90 or camera_rot == 135 then if math.abs(dir) >= 90 then inst.AnimState:SetBuild("esctemplate_right") else inst.AnimState:SetBuild("esctemplate_left") end elseif camera_rot == 180 or camera_rot == 225 then if dir >= 0 then inst.AnimState:SetBuild("esctemplate_right") else inst.AnimState:SetBuild("esctemplate_left") end else--if camera_rot == 270 or camera_rot == 315 then if math.abs(dir) <= 90 then inst.AnimState:SetBuild("esctemplate_right") else inst.AnimState:SetBuild("esctemplate_left") end end end)endBasically what we do here is change our images depending on what side the character is facing. This should work relatively well. I haven't tested it with multiple players, but it should play along nicely. Also, there is another problem: The camera can keep rotating and go out of our scope. This should be solved by using the modulo operation ( % ) and then comparing. There might be some small problems with doing an action and then rotating the camera (like fishing), but nothing gamebreaking. Link to comment Share on other sites More sharing options...
MaiTerra Posted January 9, 2015 Author Share Posted January 9, 2015 @Ryuushu Thank you! However, it seems to cause a crash whenever I enable the mod after doing this. I think I followed the instructions correctly, as far as I'm aware, though it's entirely possible I missed something since I'm still pretty new to modding and stuff. I'm assuming this code goes in the character's file in the mod's prefab folder, right? Is there anything I should make sure goes before or after this code or can it go more or less anywhere in the file? Link to comment Share on other sites More sharing options...
Ryuushu Posted January 9, 2015 Share Posted January 9, 2015 @MaiTerraIt goes in the character's prefab file, inside master_postinit. Did you remember to load the anim assets? Hmm, posting the crash screen/error line helps too. Link to comment Share on other sites More sharing options...
MaiTerra Posted January 9, 2015 Author Share Posted January 9, 2015 Oh! I got it working! The problem was that I wasn't putting it in master_postinit, silly me. But yes, it works now, thank you so much for the help! Link to comment Share on other sites More sharing options...
Ryuushu Posted January 9, 2015 Share Posted January 9, 2015 @MaiTerraI was poking around a bit more and figured out how to make idles work: --Remember to replace esctemplate with your character's prefab namelocal function ChangeAnim(inst) if inst.prefab ~= "esctemplate" or inst:HasTag("playerghost") then return end local dir = inst.Transform:GetRotation() local camera_rot = GLOBAL.TheCamera:GetHeadingTarget() % 360 print("Camera rotation:", camera_rot) print("Direction:", dir) if camera_rot == 0 or camera_rot == 45 then if dir <= -5 then inst.AnimState:SetBuild("esctemplate_right") else inst.AnimState:SetBuild("esctemplate_left") end elseif camera_rot == 90 or camera_rot == 135 then if math.abs(dir) >= 90 then inst.AnimState:SetBuild("esctemplate_right") else inst.AnimState:SetBuild("esctemplate_left") end elseif camera_rot == 180 or camera_rot == 225 then if dir >= -5 then inst.AnimState:SetBuild("esctemplate_right") else inst.AnimState:SetBuild("esctemplate_left") end else--if camera_rot == 270 or camera_rot == 315 then if math.abs(dir) <= 90 then inst.AnimState:SetBuild("esctemplate_right") else inst.AnimState:SetBuild("esctemplate_left") end endendlocal FRAMES = GLOBAL.FRAMESlocal TimeEvent = GLOBAL.TimeEventlocal function sg_postinit(sg) local old_locomote_event = sg.events.locomote.fn sg.events.locomote.fn = function(inst, data) ChangeAnim(inst) return old_locomote_event(inst, data) end if GLOBAL.TheWorld.ismastersim then local old_fishingcancel_event = sg.events.fishingcancel.fn sg.events.fishingcancel.fn = function(inst) ChangeAnim(inst) return old_fishingcancel_event(inst) end end -- Add/Remove TimeEvents as you see fitting. More TimeEvents = more accurate animations. Less TimeEvents = Slightly better performance. table.insert(sg.states["idle"].timeline, TimeEvent(20*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(30*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(40*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(50*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(60*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(70*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(80*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(90*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(100*FRAMES, function(inst) ChangeAnim(inst) end)) if GLOBAL.TheWorld.ismastersim then table.insert(sg.states["funnyidle"].timeline, TimeEvent(20*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["funnyidle"].timeline, TimeEvent(30*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["funnyidle"].timeline, TimeEvent(40*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["funnyidle"].timeline, TimeEvent(50*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["funnyidle"].timeline, TimeEvent(60*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["funnyidle"].timeline, TimeEvent(70*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["funnyidle"].timeline, TimeEvent(80*FRAMES, function(inst) ChangeAnim(inst) end)) end table.insert(sg.states["fishing"].timeline, TimeEvent(20*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["fishing"].timeline, TimeEvent(40*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["fishing"].timeline, TimeEvent(60*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["fishing"].timeline, TimeEvent(80*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["fishing"].timeline, TimeEvent(100*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["fishing"].timeline, TimeEvent(120*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["fishing"].timeline, TimeEvent(140*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["run_start"].timeline, TimeEvent(1*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["run"].timeline, TimeEvent(10*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["run"].timeline, TimeEvent(20*FRAMES, function(inst) ChangeAnim(inst) end))endAddStategraphPostInit("wilson",sg_postinit)AddStategraphPostInit("wilson_client",sg_postinit) -- Haven't tested it in the client yet.It's more or less the same except this time it hooks into the stategraph. This time it changes the animation as it moves instead changing it only at the start (when the event is received). This also means it's a bit more intensive (still better than DoTaskInTime though). With this the idle animation + rotating camera should work.I'm still unsure about how well it works with clients. Haven't been able to test it myself.Also, this time it goes into modmain, put it anywhere inside it. Link to comment Share on other sites More sharing options...
MaiTerra Posted January 10, 2015 Author Share Posted January 10, 2015 Oh, thanks! It seems to work and doesn't crash my game, so that's good! I'll have to test it out with other people when I get the chance, but this is great! Link to comment Share on other sites More sharing options...
Sabeku Posted January 16, 2015 Share Posted January 16, 2015 @MaiTerraI was poking around a bit more and figured out how to make idles work: --Remember to replace esctemplate with your character's prefab namelocal function ChangeAnim(inst) if inst.prefab ~= "esctemplate" or inst:HasTag("playerghost") then return end local dir = inst.Transform:GetRotation() local camera_rot = GLOBAL.TheCamera:GetHeadingTarget() % 360 print("Camera rotation:", camera_rot) print("Direction:", dir) if camera_rot == 0 or camera_rot == 45 then if dir <= -5 then inst.AnimState:SetBuild("esctemplate_right") else inst.AnimState:SetBuild("esctemplate_left") end elseif camera_rot == 90 or camera_rot == 135 then if math.abs(dir) >= 90 then inst.AnimState:SetBuild("esctemplate_right") else inst.AnimState:SetBuild("esctemplate_left") end elseif camera_rot == 180 or camera_rot == 225 then if dir >= -5 then inst.AnimState:SetBuild("esctemplate_right") else inst.AnimState:SetBuild("esctemplate_left") end else--if camera_rot == 270 or camera_rot == 315 then if math.abs(dir) <= 90 then inst.AnimState:SetBuild("esctemplate_right") else inst.AnimState:SetBuild("esctemplate_left") end endendlocal FRAMES = GLOBAL.FRAMESlocal TimeEvent = GLOBAL.TimeEventlocal function sg_postinit(sg) local old_locomote_event = sg.events.locomote.fn sg.events.locomote.fn = function(inst, data) ChangeAnim(inst) return old_locomote_event(inst, data) end if GLOBAL.TheWorld.ismastersim then local old_fishingcancel_event = sg.events.fishingcancel.fn sg.events.fishingcancel.fn = function(inst) ChangeAnim(inst) return old_fishingcancel_event(inst) end end -- Add/Remove TimeEvents as you see fitting. More TimeEvents = more accurate animations. Less TimeEvents = Slightly better performance. table.insert(sg.states["idle"].timeline, TimeEvent(20*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(30*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(40*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(50*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(60*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(70*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(80*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(90*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["idle"].timeline, TimeEvent(100*FRAMES, function(inst) ChangeAnim(inst) end)) if GLOBAL.TheWorld.ismastersim then table.insert(sg.states["funnyidle"].timeline, TimeEvent(20*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["funnyidle"].timeline, TimeEvent(30*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["funnyidle"].timeline, TimeEvent(40*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["funnyidle"].timeline, TimeEvent(50*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["funnyidle"].timeline, TimeEvent(60*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["funnyidle"].timeline, TimeEvent(70*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["funnyidle"].timeline, TimeEvent(80*FRAMES, function(inst) ChangeAnim(inst) end)) end table.insert(sg.states["fishing"].timeline, TimeEvent(20*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["fishing"].timeline, TimeEvent(40*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["fishing"].timeline, TimeEvent(60*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["fishing"].timeline, TimeEvent(80*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["fishing"].timeline, TimeEvent(100*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["fishing"].timeline, TimeEvent(120*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["fishing"].timeline, TimeEvent(140*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["run_start"].timeline, TimeEvent(1*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["run"].timeline, TimeEvent(10*FRAMES, function(inst) ChangeAnim(inst) end)) table.insert(sg.states["run"].timeline, TimeEvent(20*FRAMES, function(inst) ChangeAnim(inst) end))endAddStategraphPostInit("wilson",sg_postinit)AddStategraphPostInit("wilson_client",sg_postinit) -- Haven't tested it in the client yet. It's more or less the same except this time it hooks into the stategraph. This time it changes the animation as it moves instead changing it only at the start (when the event is received). This also means it's a bit more intensive (still better than DoTaskInTime though). With this the idle animation + rotating camera should work.I'm still unsure about how well it works with clients. Haven't been able to test it myself.Also, this time it goes into modmain, put it anywhere inside it. Thanks a bunch for this code. I haven't been able to test it out yet, but I plan on using it for my character, as they have an asymmetrical face.It's exactly what I was looking for! Link to comment Share on other sites More sharing options...
Recommended Posts
Archived
This topic is now archived and is closed to further replies.
Please be aware that the content of this thread may be outdated and no longer applicable.