Guest User

Unitale script for animated enemies

a guest
Jan 1st, 2016
1,725
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.95 KB | None | 0 0
  1. --[[
  2.   Script for animated enemies in Unitale
  3.   Author: /u/buttercups_pie on Reddit
  4.   Date written: 31 December 2015
  5.   Date last updated: 01 January 2016
  6.   Changelog 01 January 2016:
  7.   - Converted the script into a library. Because of this, the usage is now
  8.     completely different. Please refer to the documentation for usage changes.
  9.   - Reworked the documentation. Sorry the last version was so obtuse. I have
  10.     autism, and sometimes I accidentally say things that don't make sense to
  11.     anyone but me.
  12.   - Separated the documentation out of the code so you don't have to look at
  13.     my gross code to know how to use it.
  14. ]]
  15.  
  16. --[[
  17.   ============================================================================
  18.   Installation
  19.   ============================================================================
  20.   Store this module in [your mod directory]/Lua/Libraries and require it as
  21.   follows:
  22.     bcp = (require "bcpanim")
  23.   The module will thenceforth be available under the name "bcp". If you want,
  24.   you can also make it available under a different name. It won't break
  25.   anything.
  26.   This module is written to work when required from the encounter script.
  27.   Requiring it from other scripts might not work. If this proves to be a
  28.   problem, I'll come up with a workaround for it soon.
  29.   IMPORTANT: Once you've required the module in your encounter script, change
  30.   your Update function to include the following line!
  31.     bcp:Update()
  32.   If you don't use an Update function, and/or don't know what this means, you
  33.   can just copy the following code onto the end of your encounter script:
  34.     function Update()
  35.       bcp:Update()
  36.     end
  37. ]]
  38.  
  39. --[[
  40.   ============================================================================
  41.   Glossary
  42.   ============================================================================
  43.   "Game object": A thing that appears on the screen while the game is running.
  44.   "Game object name": Some string used in some way to identify a game object.
  45.       Any code that calls what it's animating a "game object" is general enough
  46.       that it doesn't need to know *how* exactly the object is attached to its
  47.       name, only that it is.
  48.   "Enemy": A data member of the table "enemies" available from the encounter
  49.       script. For example, enemies[1]. This counts as a game object.
  50.   "Enemy name": The definition of the variable "name" in an enemy's script.
  51.       Also the result of calling the enemy's GetVar("name") (ex.:
  52.       if enemies[1].GetVar("name") == "Froggit" then DEBUG("it's a frog") end).
  53.       This name will typically be capitalized. It is NOT necessarily the
  54.       string used in the encounter script to tell the engine which enemy
  55.       script to load, which will usually be lowercase, and may have underscores
  56.       instead of spaces. So, to clarify, that's the name you DON'T want. If
  57.       something asks for an "enemy name", the name you DO want is the one that
  58.       will appear on the screen in-game when you fight the enemy. I have yet
  59.       to figure out a workaround for if this changes during a battle, so
  60.       currently battles with enemies that change names are unsupported. I'll
  61.       deal with this soon.
  62.   "State entry": In the Animations table, these are used to manage how each
  63.       individual game object is animating. Each entry corresponds to one object.
  64.       (There are some entries that don't correspond to objects, but you won't
  65.       have to deal with those.)
  66.   "Data entry": In the Animations table, these are used to manage the actual
  67.       sequences of frames that comprise the animations for each game object.
  68.       Each entry corresponds to one kind of object. (For example, you might
  69.       have multiple Froggits that each have their own state entries, but since
  70.       they're all Froggits, they'll all use the same data entry.)
  71.   "Method": This is a standard Lua thing, but it's obscure enough that I feel
  72.       it bears mentioning. If you see a function f on a table t, and the
  73.       documentation says f is called a "method" instead of a "function", that
  74.       means you call it with t:f(args) instead of t.f(args). This ensures that
  75.       it maintains awareness of what object it belongs to, rather than being
  76.       called as a pure function.
  77. ]]
  78.  
  79. --[[
  80.   ============================================================================
  81.   Functions and Objects
  82.   ============================================================================
  83.   Only the functions that are designed to be used by the end user of the script
  84.   will be documented. In case people have need of this module's internal
  85.   functions, I'll document them, too, but I'll do it later.
  86.   ----------------------------------------------------------------
  87.   bcp.Animations.Add -- Method
  88.   ----------------------------------------------------------------
  89.   Usage: bcp.Animations:Add(stname, animname, fname, dur)
  90.     (mind the colon; this is a method, see the glossary for more information)
  91.   Should be called from: parameter function to bcp.DefineAnimations (see below)
  92.   Purpose:
  93.     Adds a frame to an animation for a game object. Creates a data entry and/or
  94.     animation to add to if they don't already exist under the given names.
  95.   Arguments:
  96.   * stname: String. The enemy name for the enemy whose animation you're adding
  97.     a frame to.
  98.   * animname: String. The name of the animation you want to add to. (As noted
  99.     under "purpose", adding to an animation for the first time creates it.)
  100.   * fname: String. The filename of the image to add into the animation.
  101.     (No path or file extension; path is assumed to be the Sprites directory,
  102.     and file extension is assumed to be PNG, since that's how Unitale works.)
  103.   * dur: Number. The number of seconds to display the frame for. May be a non-
  104.     integer. For most purposes, I highly recommend numbers less than 1, as one
  105.     second is an awful long time to display a single frame of animation.
  106.   Return value: Nil.
  107.   Example:
  108.     bcp.Animations:Add("Froggit", "hop", "froghop1", 0.125)
  109.     -- Adds a frame to Froggit's hop animation. Ensures that the frame will be
  110.     -- loaded from froghop1.png and displayed for an eighth of a second.
  111.   ----------------------------------------------------------------
  112.   bcp.Animations.Schedule -- Method
  113.   ----------------------------------------------------------------
  114.   Usage: bcp.Animations:Schedule(stname, animname, nextname)
  115.   Should be called from: parameter function to bcp.DefineAnimations (see below)
  116.   Purpose:
  117.     Sets one of a game object's animations to run after another one. By
  118.     default, all animations are set to run after themselves, so that they'll
  119.     loop.
  120.   Arguments:
  121.   * stname: String. The enemy name for the enemy whose animations you're
  122.     handling.
  123.   * animname: String. The name of the animation you're setting a next animation
  124.     for.
  125.   * nextname: String. The name of the animation you want animname to lead into.
  126.   Return value: Nil.
  127.   Example:
  128.     bcp.Animations:Schedule("Froggit", "hop", "idle")
  129.     -- Ensures that when a Froggit is done hopping, it will sit back down,
  130.     -- rather than continuing to hop indefinitely.
  131.   ----------------------------------------------------------------
  132.   bcp.PauseEnemyAnimation -- Method
  133.   ----------------------------------------------------------------
  134.   Usage: bcp:RegisterEnemyAnimations(i)
  135.   Should be called from: the encounter script and/or its functions
  136.   Purpose:
  137.     Causes an enemy to stop animating and thenceforth display a still frame.
  138.   Arguments:
  139.   * i: Number. The index of the enemy you want to pause. That is to say, the
  140.     enemy which will be paused is enemies[i].
  141.   Return value: Nil.
  142.   Example:
  143.     bcp:PauseEnemyAnimation(1)
  144.     BattleDialog({"Froggit is frozen in time!"})
  145.   ----------------------------------------------------------------
  146.   bcp.ResumeEnemyAnimation -- Method
  147.   ----------------------------------------------------------------
  148.   Usage: bcp:ResumeEnemyAnimations(i)
  149.   Should be called from: the encounter script and/or its functions
  150.   Purpose:
  151.     Causes a paused enemy to continue animating where it left off. Has no
  152.     effect on enemies that are already animating.
  153.   Arguments:
  154.   * i: Number. The index of the enemy you want to resume. That is to say, the
  155.     enemy which will be resumed is enemies[i].
  156.   Return value: Nil.
  157.   Example:
  158.     bcp:ResumeEnemyAnimation(1)
  159.     BattleDialog({"Froggit can move again!"})
  160.   ----------------------------------------------------------------
  161.   bcp.ChangeEnemyAnimation -- Method
  162.   ----------------------------------------------------------------
  163.   Usage: bcp:ChangeEnemyAnimations(i, newanim)
  164.   Should be called from: the encounter script and/or its functions
  165.   Purpose:
  166.     Causes an enemy to switch to another animation. Starts the animation from
  167.     the beginning. If called with the current animation, restarts the
  168.     animation. By default, each enemy will start out displaying the first
  169.     animation you added to it.
  170.   Arguments:
  171.   * i: Number. The index of the enemy whose animation you want to change. That
  172.     is to say, the enemy whose animation will be changed is enemies[i].
  173.   * newanim: String. The name of the animation you're changing to.
  174.   Return value: Nil.
  175.   Example:
  176.     bcp:ChangeEnemyAnimation(1, "hop")
  177.     -- Causes the Froggit to do a little hop. Since we've decided above that
  178.     -- the hop animation is followed by the idle animation, the Froggit will
  179.     -- stop hopping upon landing. Therefore, this command could be useful
  180.     -- at the beginning of the Froggit's attack phase, to demonstrate
  181.     -- aggression. It could also be useful if the Froggit were to dodge an
  182.     -- attack, to indicate jumping over it.
  183.   ----------------------------------------------------------------
  184.   bcp.DefineAnimations -- Method
  185.   ----------------------------------------------------------------
  186.   Usage: bcp:DefineAnimations(func)
  187.   Should be called from: the encounter script at top level (not inside any
  188.     functions)
  189.   Purpose:
  190.     Prepares to define all the animations you'll be using in your mod. Will
  191.     define the animations in the first game cycle and immediately begin using
  192.     them.
  193.   Arguments:
  194.   * func: Function. You should put all your calls to Add and Schedule here.
  195.     (See above.)
  196.   Return value: Nil.
  197.   Example:
  198.     bcp:DefineAnimations(function ()
  199.       bcp.Animations:Add("Froggit", "idle", "frogidle1", 0.25)
  200.       bcp.Animations:Add("Froggit", "idle", "frogidle2", 0.125)
  201.       bcp.Animations:Add("Froggit", "idle", "frogidle3", 0.25)
  202.       bcp.Animations:Add("Froggit", "idle", "frogidle2", 0.125)
  203.       bcp.Animations:Add("Froggit", "hop", "froghop1", 0.125)
  204.       bcp.Animations:Add("Froggit", "hop", "froghop2", 0.125)
  205.       bcp.Animations:Add("Froggit", "hop", "froghop3", 0.125)
  206.       bcp.Animations:Add("Froggit", "hop", "froghop4", 0.125)
  207.       bcp.Animations:Schedule("Froggit", "hop", "idle")
  208.     end)
  209.     -- This code will allow the module to define Froggit's animations. The
  210.     -- idle and hop animations will be define.
  211.   ----------------------------------------------------------------
  212.   bcp.OnInitialize -- Method
  213.   ----------------------------------------------------------------
  214.   Usage: bcp:OnInitialize(func)
  215.   Should be called from: the encounter script at top level (not inside any
  216.     functions)
  217.   Purpose:
  218.     Prepares to execute arbitrary commands in the first game cycle, after the
  219.     animations have been defined.
  220.   Arguments:
  221.   * func: Function. Here you should put any commands you want to run at the
  222.     beginning of the battle, but can't just throw into your encounter script.
  223.   Return value: Nil.
  224.   Example:
  225.     bcp:OnInitialize(function ()
  226.       enemies[1].SetVar("currentdialogue", {
  227.         "It's a beautiful day\noutside.",
  228.         "Birds are singing.",
  229.         "Flowers are blooming.",
  230.         "On days like these,\nkids like you...",
  231.         "O B L I G A T O R Y\nB A D  T I M E\nR E F E R E N C E."
  232.       })
  233.       State(EnemyDialogue)
  234.     end)
  235.     -- This code will ensure that the battle starts off with Sans's speech,
  236.     -- rather than the encounter text. You couldn't have just thrown this into
  237.     -- your encounter script, because Sans needs to exist before you can
  238.     -- SetVars on him, and the battle itself needs to exist before you can
  239.     -- change its State.
  240.   ----------------------------------------------------------------
  241.   bcp.Update -- Method
  242.   ----------------------------------------------------------------
  243.   Usage: bcp:Update()
  244.   Should be called from: the encounter script's Update() function (This is
  245.     the most important part of using this module!)
  246.   Purpose:
  247.     Notifies the module that a game cycle is in progress.
  248.   Arguments: None.
  249.   Return value: Nil.
  250.   Example:
  251.     function Update()
  252.       bcp:Update()
  253.     end
  254.     -- This code will ensure that the animations get updated every time the
  255.     -- game gets updated. Something like this should definitely be in your
  256.     -- Update() function if you mean to use this module.
  257. ]]
  258.  
  259. --[[
  260.   ============================================================================
  261.   Weird Spaghetti Code
  262.   ============================================================================
  263.   Move along, nothing to see here. > - >
  264. ]]
  265. local bcp = {}
  266. bcp.DeepCopy = function (self, tab)
  267.   if type(tab) ~= "table" then return tab end
  268.   local tab2 = {}
  269.   for k, v in pairs(tab) do
  270.     tab2[k] = self:DeepCopy(v)
  271.   end
  272.   return tab2
  273. end
  274. bcp.Animations = {
  275.   owner = bcp,
  276.   counter = 1,
  277.   Data = {},
  278.   State = {},
  279.   Add = (function (self, stname, animname, fname, dur)
  280.     if self.Data[stname] == nil then
  281.       self.Data[stname] = {}
  282.       self.State[stname] = {
  283.         realname = stname,
  284.         currentanim = animname,
  285.         currentframe = 1,
  286.         secssincechange = 0,
  287.         playing = true
  288.       }
  289.     end
  290.     if self.Data[stname][animname] == nil then
  291.       self.Data[stname][animname] = {
  292.         framelist = {},
  293.         nextanim = animname
  294.       }
  295.     end
  296.     local addto = self.Data[stname][animname].framelist
  297.     addto[#addto + 1] = {
  298.       filename = fname,
  299.       duration = dur
  300.     }
  301.   end),
  302.   Schedule = (function (self, stname, animname, nextname)
  303.     self.Data[stname][animname].nextanim = nextname
  304.   end),
  305.   Poll = (function (self, stname)
  306.     local data = self.Data[self.State[stname].realname]
  307.     local state = self.State[stname]
  308.     local retval =
  309.       data[state.currentanim].framelist[state.currentframe].filename
  310.     if state.playing then
  311.       state.secssincechange = state.secssincechange + Time.dt
  312.       if state.secssincechange >=
  313.          data[state.currentanim].framelist[state.currentframe].duration then
  314.         state.secssincechange = 0
  315.         state.currentframe = state.currentframe + 1
  316.         if state.currentframe > #(data[state.currentanim].framelist) then
  317.           state.currentframe = 1
  318.           state.currentanim = data[state.currentanim].nextanim
  319.         end
  320.       end
  321.     end
  322.     return retval
  323.   end),
  324.   Pause = (function (self, stname)
  325.     self.State[stname].playing = false
  326.   end),
  327.   Resume = (function (self, stname)
  328.     self.State[stname].playing = true
  329.   end),
  330.   Change = (function (self, stname, newanim)
  331.     local state = self.State[stname]
  332.     state.currentanim = newanim
  333.     state.currentframe = 1
  334.     state.tickssincechange = 0
  335.   end),
  336.   Copy = (function (self, stname)
  337.     local newstname = stname .. "_COPY" .. self.counter
  338.     self.State[newstname] = self.owner:DeepCopy(self.State[stname])
  339.     self.counter = self.counter + 1
  340.     return newstname
  341.   end)
  342. }
  343. bcp.RegisterEnemyAnimations = function (self, i)
  344.   enemies[i].SetVar("animtable",
  345.                     self.Animations:Copy(enemies[i].GetVar("name")))
  346. end
  347. bcp.RegisterAllEnemyAnimations = function (self)
  348.   for k, v in pairs(enemies) do
  349.     if type(k) == "number" then self:RegisterEnemyAnimations(k) end
  350.   end
  351. end
  352. bcp.UpdateEnemyAnimation = function (self, i)
  353.   enemies[i].SetSprite(self.Animations:Poll(enemies[i].GetVar("animtable")))
  354. end
  355. bcp.UpdateAllEnemyAnimations = function (self)
  356.   for k, v in pairs(enemies) do
  357.     if type(k) == "number" then self:UpdateEnemyAnimation(k) end
  358.   end
  359. end
  360. bcp.PauseEnemyAnimation = function (self, i)
  361.   self.Animations:Pause(enemies[i].GetVar("animtable"))
  362. end
  363. bcp.ResumeEnemyAnimation = function (self, i)
  364.   self.Animations:Resume(enemies[i].GetVar("animtable"))
  365. end
  366. bcp.ChangeEnemyAnimation = function (self, i, newanim)
  367.   self.Animations:Change(enemies[i].GetVar("animtable"), newanim)
  368. end
  369. bcp.animationdefinitionhandler = function ()
  370. end
  371. bcp.DefineAnimations = function (self, func)
  372.   self.animationdefinitionhandler = func
  373. end
  374. bcp.initializationhandler = function ()
  375. end
  376. bcp.OnInitialize = function (self, func)
  377.   self.initializationhandler = func
  378. end
  379. bcp.initialized = false
  380. bcp.Initialize = function (self)
  381.   self.animationdefinitionhandler()
  382.   self:RegisterAllEnemyAnimations()
  383.   self.initializationhandler()
  384.   self.initialized = true
  385. end
  386. bcp.Update = function (self)
  387.   if not self.initialized then self:Initialize() end
  388.   self:UpdateAllEnemyAnimations()
  389. end
  390. return bcp
Advertisement
Add Comment
Please, Sign In to add comment