Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- Script for animated enemies in Unitale
- Author: /u/buttercups_pie on Reddit
- Date written: 31 December 2015
- Date last updated: 01 January 2016
- Changelog 01 January 2016:
- - Converted the script into a library. Because of this, the usage is now
- completely different. Please refer to the documentation for usage changes.
- - Reworked the documentation. Sorry the last version was so obtuse. I have
- autism, and sometimes I accidentally say things that don't make sense to
- anyone but me.
- - Separated the documentation out of the code so you don't have to look at
- my gross code to know how to use it.
- ]]
- --[[
- ============================================================================
- Installation
- ============================================================================
- Store this module in [your mod directory]/Lua/Libraries and require it as
- follows:
- bcp = (require "bcpanim")
- The module will thenceforth be available under the name "bcp". If you want,
- you can also make it available under a different name. It won't break
- anything.
- This module is written to work when required from the encounter script.
- Requiring it from other scripts might not work. If this proves to be a
- problem, I'll come up with a workaround for it soon.
- IMPORTANT: Once you've required the module in your encounter script, change
- your Update function to include the following line!
- bcp:Update()
- If you don't use an Update function, and/or don't know what this means, you
- can just copy the following code onto the end of your encounter script:
- function Update()
- bcp:Update()
- end
- ]]
- --[[
- ============================================================================
- Glossary
- ============================================================================
- "Game object": A thing that appears on the screen while the game is running.
- "Game object name": Some string used in some way to identify a game object.
- Any code that calls what it's animating a "game object" is general enough
- that it doesn't need to know *how* exactly the object is attached to its
- name, only that it is.
- "Enemy": A data member of the table "enemies" available from the encounter
- script. For example, enemies[1]. This counts as a game object.
- "Enemy name": The definition of the variable "name" in an enemy's script.
- Also the result of calling the enemy's GetVar("name") (ex.:
- if enemies[1].GetVar("name") == "Froggit" then DEBUG("it's a frog") end).
- This name will typically be capitalized. It is NOT necessarily the
- string used in the encounter script to tell the engine which enemy
- script to load, which will usually be lowercase, and may have underscores
- instead of spaces. So, to clarify, that's the name you DON'T want. If
- something asks for an "enemy name", the name you DO want is the one that
- will appear on the screen in-game when you fight the enemy. I have yet
- to figure out a workaround for if this changes during a battle, so
- currently battles with enemies that change names are unsupported. I'll
- deal with this soon.
- "State entry": In the Animations table, these are used to manage how each
- individual game object is animating. Each entry corresponds to one object.
- (There are some entries that don't correspond to objects, but you won't
- have to deal with those.)
- "Data entry": In the Animations table, these are used to manage the actual
- sequences of frames that comprise the animations for each game object.
- Each entry corresponds to one kind of object. (For example, you might
- have multiple Froggits that each have their own state entries, but since
- they're all Froggits, they'll all use the same data entry.)
- "Method": This is a standard Lua thing, but it's obscure enough that I feel
- it bears mentioning. If you see a function f on a table t, and the
- documentation says f is called a "method" instead of a "function", that
- means you call it with t:f(args) instead of t.f(args). This ensures that
- it maintains awareness of what object it belongs to, rather than being
- called as a pure function.
- ]]
- --[[
- ============================================================================
- Functions and Objects
- ============================================================================
- Only the functions that are designed to be used by the end user of the script
- will be documented. In case people have need of this module's internal
- functions, I'll document them, too, but I'll do it later.
- ----------------------------------------------------------------
- bcp.Animations.Add -- Method
- ----------------------------------------------------------------
- Usage: bcp.Animations:Add(stname, animname, fname, dur)
- (mind the colon; this is a method, see the glossary for more information)
- Should be called from: parameter function to bcp.DefineAnimations (see below)
- Purpose:
- Adds a frame to an animation for a game object. Creates a data entry and/or
- animation to add to if they don't already exist under the given names.
- Arguments:
- * stname: String. The enemy name for the enemy whose animation you're adding
- a frame to.
- * animname: String. The name of the animation you want to add to. (As noted
- under "purpose", adding to an animation for the first time creates it.)
- * fname: String. The filename of the image to add into the animation.
- (No path or file extension; path is assumed to be the Sprites directory,
- and file extension is assumed to be PNG, since that's how Unitale works.)
- * dur: Number. The number of seconds to display the frame for. May be a non-
- integer. For most purposes, I highly recommend numbers less than 1, as one
- second is an awful long time to display a single frame of animation.
- Return value: Nil.
- Example:
- bcp.Animations:Add("Froggit", "hop", "froghop1", 0.125)
- -- Adds a frame to Froggit's hop animation. Ensures that the frame will be
- -- loaded from froghop1.png and displayed for an eighth of a second.
- ----------------------------------------------------------------
- bcp.Animations.Schedule -- Method
- ----------------------------------------------------------------
- Usage: bcp.Animations:Schedule(stname, animname, nextname)
- Should be called from: parameter function to bcp.DefineAnimations (see below)
- Purpose:
- Sets one of a game object's animations to run after another one. By
- default, all animations are set to run after themselves, so that they'll
- loop.
- Arguments:
- * stname: String. The enemy name for the enemy whose animations you're
- handling.
- * animname: String. The name of the animation you're setting a next animation
- for.
- * nextname: String. The name of the animation you want animname to lead into.
- Return value: Nil.
- Example:
- bcp.Animations:Schedule("Froggit", "hop", "idle")
- -- Ensures that when a Froggit is done hopping, it will sit back down,
- -- rather than continuing to hop indefinitely.
- ----------------------------------------------------------------
- bcp.PauseEnemyAnimation -- Method
- ----------------------------------------------------------------
- Usage: bcp:RegisterEnemyAnimations(i)
- Should be called from: the encounter script and/or its functions
- Purpose:
- Causes an enemy to stop animating and thenceforth display a still frame.
- Arguments:
- * i: Number. The index of the enemy you want to pause. That is to say, the
- enemy which will be paused is enemies[i].
- Return value: Nil.
- Example:
- bcp:PauseEnemyAnimation(1)
- BattleDialog({"Froggit is frozen in time!"})
- ----------------------------------------------------------------
- bcp.ResumeEnemyAnimation -- Method
- ----------------------------------------------------------------
- Usage: bcp:ResumeEnemyAnimations(i)
- Should be called from: the encounter script and/or its functions
- Purpose:
- Causes a paused enemy to continue animating where it left off. Has no
- effect on enemies that are already animating.
- Arguments:
- * i: Number. The index of the enemy you want to resume. That is to say, the
- enemy which will be resumed is enemies[i].
- Return value: Nil.
- Example:
- bcp:ResumeEnemyAnimation(1)
- BattleDialog({"Froggit can move again!"})
- ----------------------------------------------------------------
- bcp.ChangeEnemyAnimation -- Method
- ----------------------------------------------------------------
- Usage: bcp:ChangeEnemyAnimations(i, newanim)
- Should be called from: the encounter script and/or its functions
- Purpose:
- Causes an enemy to switch to another animation. Starts the animation from
- the beginning. If called with the current animation, restarts the
- animation. By default, each enemy will start out displaying the first
- animation you added to it.
- Arguments:
- * i: Number. The index of the enemy whose animation you want to change. That
- is to say, the enemy whose animation will be changed is enemies[i].
- * newanim: String. The name of the animation you're changing to.
- Return value: Nil.
- Example:
- bcp:ChangeEnemyAnimation(1, "hop")
- -- Causes the Froggit to do a little hop. Since we've decided above that
- -- the hop animation is followed by the idle animation, the Froggit will
- -- stop hopping upon landing. Therefore, this command could be useful
- -- at the beginning of the Froggit's attack phase, to demonstrate
- -- aggression. It could also be useful if the Froggit were to dodge an
- -- attack, to indicate jumping over it.
- ----------------------------------------------------------------
- bcp.DefineAnimations -- Method
- ----------------------------------------------------------------
- Usage: bcp:DefineAnimations(func)
- Should be called from: the encounter script at top level (not inside any
- functions)
- Purpose:
- Prepares to define all the animations you'll be using in your mod. Will
- define the animations in the first game cycle and immediately begin using
- them.
- Arguments:
- * func: Function. You should put all your calls to Add and Schedule here.
- (See above.)
- Return value: Nil.
- Example:
- bcp:DefineAnimations(function ()
- bcp.Animations:Add("Froggit", "idle", "frogidle1", 0.25)
- bcp.Animations:Add("Froggit", "idle", "frogidle2", 0.125)
- bcp.Animations:Add("Froggit", "idle", "frogidle3", 0.25)
- bcp.Animations:Add("Froggit", "idle", "frogidle2", 0.125)
- bcp.Animations:Add("Froggit", "hop", "froghop1", 0.125)
- bcp.Animations:Add("Froggit", "hop", "froghop2", 0.125)
- bcp.Animations:Add("Froggit", "hop", "froghop3", 0.125)
- bcp.Animations:Add("Froggit", "hop", "froghop4", 0.125)
- bcp.Animations:Schedule("Froggit", "hop", "idle")
- end)
- -- This code will allow the module to define Froggit's animations. The
- -- idle and hop animations will be define.
- ----------------------------------------------------------------
- bcp.OnInitialize -- Method
- ----------------------------------------------------------------
- Usage: bcp:OnInitialize(func)
- Should be called from: the encounter script at top level (not inside any
- functions)
- Purpose:
- Prepares to execute arbitrary commands in the first game cycle, after the
- animations have been defined.
- Arguments:
- * func: Function. Here you should put any commands you want to run at the
- beginning of the battle, but can't just throw into your encounter script.
- Return value: Nil.
- Example:
- bcp:OnInitialize(function ()
- enemies[1].SetVar("currentdialogue", {
- "It's a beautiful day\noutside.",
- "Birds are singing.",
- "Flowers are blooming.",
- "On days like these,\nkids like you...",
- "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."
- })
- State(EnemyDialogue)
- end)
- -- This code will ensure that the battle starts off with Sans's speech,
- -- rather than the encounter text. You couldn't have just thrown this into
- -- your encounter script, because Sans needs to exist before you can
- -- SetVars on him, and the battle itself needs to exist before you can
- -- change its State.
- ----------------------------------------------------------------
- bcp.Update -- Method
- ----------------------------------------------------------------
- Usage: bcp:Update()
- Should be called from: the encounter script's Update() function (This is
- the most important part of using this module!)
- Purpose:
- Notifies the module that a game cycle is in progress.
- Arguments: None.
- Return value: Nil.
- Example:
- function Update()
- bcp:Update()
- end
- -- This code will ensure that the animations get updated every time the
- -- game gets updated. Something like this should definitely be in your
- -- Update() function if you mean to use this module.
- ]]
- --[[
- ============================================================================
- Weird Spaghetti Code
- ============================================================================
- Move along, nothing to see here. > - >
- ]]
- local bcp = {}
- bcp.DeepCopy = function (self, tab)
- if type(tab) ~= "table" then return tab end
- local tab2 = {}
- for k, v in pairs(tab) do
- tab2[k] = self:DeepCopy(v)
- end
- return tab2
- end
- bcp.Animations = {
- owner = bcp,
- counter = 1,
- Data = {},
- State = {},
- Add = (function (self, stname, animname, fname, dur)
- if self.Data[stname] == nil then
- self.Data[stname] = {}
- self.State[stname] = {
- realname = stname,
- currentanim = animname,
- currentframe = 1,
- secssincechange = 0,
- playing = true
- }
- end
- if self.Data[stname][animname] == nil then
- self.Data[stname][animname] = {
- framelist = {},
- nextanim = animname
- }
- end
- local addto = self.Data[stname][animname].framelist
- addto[#addto + 1] = {
- filename = fname,
- duration = dur
- }
- end),
- Schedule = (function (self, stname, animname, nextname)
- self.Data[stname][animname].nextanim = nextname
- end),
- Poll = (function (self, stname)
- local data = self.Data[self.State[stname].realname]
- local state = self.State[stname]
- local retval =
- data[state.currentanim].framelist[state.currentframe].filename
- if state.playing then
- state.secssincechange = state.secssincechange + Time.dt
- if state.secssincechange >=
- data[state.currentanim].framelist[state.currentframe].duration then
- state.secssincechange = 0
- state.currentframe = state.currentframe + 1
- if state.currentframe > #(data[state.currentanim].framelist) then
- state.currentframe = 1
- state.currentanim = data[state.currentanim].nextanim
- end
- end
- end
- return retval
- end),
- Pause = (function (self, stname)
- self.State[stname].playing = false
- end),
- Resume = (function (self, stname)
- self.State[stname].playing = true
- end),
- Change = (function (self, stname, newanim)
- local state = self.State[stname]
- state.currentanim = newanim
- state.currentframe = 1
- state.tickssincechange = 0
- end),
- Copy = (function (self, stname)
- local newstname = stname .. "_COPY" .. self.counter
- self.State[newstname] = self.owner:DeepCopy(self.State[stname])
- self.counter = self.counter + 1
- return newstname
- end)
- }
- bcp.RegisterEnemyAnimations = function (self, i)
- enemies[i].SetVar("animtable",
- self.Animations:Copy(enemies[i].GetVar("name")))
- end
- bcp.RegisterAllEnemyAnimations = function (self)
- for k, v in pairs(enemies) do
- if type(k) == "number" then self:RegisterEnemyAnimations(k) end
- end
- end
- bcp.UpdateEnemyAnimation = function (self, i)
- enemies[i].SetSprite(self.Animations:Poll(enemies[i].GetVar("animtable")))
- end
- bcp.UpdateAllEnemyAnimations = function (self)
- for k, v in pairs(enemies) do
- if type(k) == "number" then self:UpdateEnemyAnimation(k) end
- end
- end
- bcp.PauseEnemyAnimation = function (self, i)
- self.Animations:Pause(enemies[i].GetVar("animtable"))
- end
- bcp.ResumeEnemyAnimation = function (self, i)
- self.Animations:Resume(enemies[i].GetVar("animtable"))
- end
- bcp.ChangeEnemyAnimation = function (self, i, newanim)
- self.Animations:Change(enemies[i].GetVar("animtable"), newanim)
- end
- bcp.animationdefinitionhandler = function ()
- end
- bcp.DefineAnimations = function (self, func)
- self.animationdefinitionhandler = func
- end
- bcp.initializationhandler = function ()
- end
- bcp.OnInitialize = function (self, func)
- self.initializationhandler = func
- end
- bcp.initialized = false
- bcp.Initialize = function (self)
- self.animationdefinitionhandler()
- self:RegisterAllEnemyAnimations()
- self.initializationhandler()
- self.initialized = true
- end
- bcp.Update = function (self)
- if not self.initialized then self:Initialize() end
- self:UpdateAllEnemyAnimations()
- end
- return bcp
Advertisement
Add Comment
Please, Sign In to add comment