UV Animation made easy [GUIDE][V1.1]

Hello BA!

In most of my games I need to use animated textures, there are an function in blender to do this under the UV/Image Editor… this feature do however (according to me) suck. It gives you no control at all. This is why I have implemented an script/python library to do this exact thing, scroll and animate textures.

as for now my script allows the user to control:

  • start frame/position [updated in V1.1]
  • Direction [updated in V1.1]
  • set speed
  • change speedusing prop [NEW/updated in V1.1]
  • set frame (only in python)
  • Turn On/Off in real-time (controlled on the parent OR the main object) [NEW in V1.1]

The library are implemented so that multiply objects can use the same script without interfering.

Here is an guide on how to setup my UV scroll script (this guide requires basic knowledge of blender GE):

Step 1:
add an new plane (or any other object, but plane is most easy)

Step 2:
unwrap the plane



… load an sheet-image (or any image) in the UV/Image editor and position the UV-map so that it covers the bottom left square/corner of your image (as shown in the image below) THIS PART IS IMPORTANT!


Step 3:
give your plane an material (set it up as you like), go to the texture tab and add the texture. Set the texture to use the UV map that you made earlier (as in scream below):


Step 4:
open the Text Editor, create an new Text Datablock and name it UVScroll.py

past this code in the text Datablock:


#########################################################
#UV scroll by Tony Eriksson (esoneson on blenderartists)
#Use it as you like, to whatever you like, just keep this credit.
#...and if you like it, tell me :)
#########################################################

from bge import logic as GL

scrollers = {} #global dictionary to keep track of all the objects using uv-scroll

#the function to run from the bge
def Scroll_Object():
    global scrollers
    cont = GL.getCurrentController() 
    own = cont.owner
    if own in scrollers: #if alredy in dictionary, run scroll function
        scrollers[own].ScrollUV()
    else:    #if NOT in dictionary, add to dict and run needed functions
        inv = False
        speed = 1
        if 'Invert' in own:
            inv = own["Invert"]
        if 'Speed' in own:
            speed = own["Speed"]
        scroller = Scroller(own["X"],own["Y"],own["X_Start"],own["Y_Start"],inv,speed)
        scrollers[own] = scroller
        scroller.firstRun()
        scroller.SetStartPosition()
        scroller.ScrollUV()

#the Scroller class, this is where the magic happens
#it is called as an object by the scroll_object function
class Scroller:
    #init the scroller by setting needed values
    def __init__(self, MX, MY, CX=1, CY=1, Inv=False,speed=1):
        self.MaxX = MX
        self.MaxY = MY
        self.Invert = Inv
        self.CurrentX = CX
        self.CurrentY = CY
        self.Speed = speed
        self.StandardX = list()
        self.StandardY = list()
        self.CStep = 1
        self.cont = GL.getCurrentController() 
        self.own = self.cont.owner 
        self.mesh = self.own.meshes[0]
        self.array = self.mesh.getVertexArrayLength(0)
        self.GotSensor = self.selfSensorChek()
    #sets the speed of the scroll
    def SetSpeed(self,Speed):
        self.Speed = Speed
    #Updating the invert chek
    def UpdateInvertValues(self):
        try:
            self.Invert = self.own["Invert"]
        except:
            pass
    #checking if the scroll are to be run
    #return true if it is, else false 
    def Speed_f(self):
        self.UpdateInvertValues()
        if self.CStep >= self.Speed:
            self.CStep = 0
            return True
        else:
            self.CStep += 1
            return False
    #set the start position of the UV
    def SetStartPosition(self):
        for v in range(0,self.array):
            vert = self.mesh.getVertex(0,v) 
            uv = vert.getUV()
            uv[0] = self.StandardX[v] + ((1/self.MaxX) * (self.CurrentX-1))
            uv[1] = self.StandardY[v] + ((1/self.MaxY) * (self.CurrentY-1))
            vert.setUV(uv)
    #getting the StandardX and StandardY values, this need to be run before any other UV operation
    #(only need to be run once on every object)
    def firstRun(self):
        for v in range(0,self.array):
            vert = self.mesh.getVertex(0,v) 
            uv = vert.getUV() 
            self.StandardY.append(uv[1])
            self.StandardX.append(uv[0])
    #This function are locking for the Controller prop in the Parent of this object
    #returns 1 if the controller don't exist, 2 if the controller is False and 3 if it is True
    def ParentSensorChek(self):
        try:
            if 'Controller' in self.own.parent:
                if self.own.parent["Controller"] == True:
                    return 3
                else:
                    return 2
            else:
                return 1
        except:
            return 1
    #checking if the prop Controller exists , and if so, what value it holds
    #returns 1 if the controller don't exist, 2 if the controller is False and 3 if it is True
    def selfSensorChek(self):
        try:
            if self.own["Controller"] == True:
                return 3
            else:
                return 2
        except:
            return 1
    #the UV scroll function, this function uses the other functions in this class to move the uv.
    #NOTE: the uv movement is based on percentage of the texture
    def ScrollUV(self):
        if self.Speed_f():
            goon = 1
            if self.GotSensor != 1:
                if self.selfSensorChek() == 3:
                    goon = 3
                else:
                    goon = 2
            if goon == 1:
                if self.ParentSensorChek() != 2:
                    goon = 3
            if goon == 3:
                for v in range(0,self.array):
                    vert = self.mesh.getVertex(0,v) 
                    uv = vert.getUV() 
                    if self.Invert == False: #is the uv scroll inverted?
                        if self.CurrentX < self.MaxX:
                            uv[0] += (1/self.MaxX)
                        else:
                            uv[0] = self.StandardX[v]
                            if self.CurrentY < self.MaxY:
                                uv[1] += (1/self.MaxY)
                            else:
                                uv[1] = self.StandardY[v]
                    else:
                        if self.CurrentX > 1:
                            uv[0] -= (1/self.MaxX)
                        else:
                            uv[0] = self.StandardX[v] + (1/self.MaxX)*(self.MaxX-1) #?
                            if self.CurrentY > 1:
                                uv[1] -= (1/self.MaxY)
                            else:
                                uv[1] = self.StandardY[v] + (1/self.MaxY)*(self.MaxY-1) #?
                    vert.setUV(uv)
                if self.Invert == False: #is the uv scroll inverted?
                    if self.CurrentX < self.MaxX:
                        self.CurrentX += 1
                    else:
                        self.CurrentX = 1
                        if self.CurrentY < self.MaxY:
                            self.CurrentY += 1
                        else:
                            self.CurrentY = 1
                else:
                    if self.CurrentX > 1:
                        self.CurrentX -= 1
                    else:
                        self.CurrentX = self.MaxX
                        if self.CurrentY > 1:
                            self.CurrentY -= 1
                        else:
                            self.CurrentY = self.MaxY

as shown in the image below (see next post):


Step 5:
Go to the Logic Editor and add an always sensor, set this one to TRUE pulse mode. Add an Python Controller , set this one to Module-Mode and enter UVScroll.Scroll_Object in it. connect the always to the python controller.
Like in the image below:




Step 6:

now lock at the Properties (still in the Logic Editor).
add the flowing Properties:

(Needed)
Name: “X” type:“Integer” Value: (number of rows that your image are suppose to move on the X-axie, it is 4 in the example)
Name: “Y” type:“Integer” Value: (number of rows that your image are suppose to move on the Y-axie, it is 2 in the example)

(optional)
Name: “X_Start” type:“Integer” Value: (on what square the animation are to start on the X-axie, it is 1 in the example)
Name: “Y_Start” type:“Integer” Value: (on what square the animation are to start on the Y-axie, it is 2 in the example)
Name: “Invert” type:“Boolean” Value: (TRUE or FALSE , used to invert the animation)
Name: “Speed” type:“Integer” Value: (positive int, smaller values makes the animation go faster)

(Optional - NEW in V1.1)
Name: “Controller” type:“Boolean” Value: (TRUE or FALSE , used to turn the animation on or off in real-time) - info about this prop at the bottom.

Step 7:
DONE! , start the game engine and look at your animated texture!

====================================================

usual errors:

  • something got the wrong name
  • you are not in GLSL mode (don’t know if t work whitout it?)
  • you need to turn Textures on in the shade/display bar

here are an demo file (version 1.0): (v1.1 demo coming soon…)
Demo.blend (577 KB) NOTE: the demo is still using version 1.0 use the code in this post in order to use Version 1.1

if you like this guide or would like me to change anything, plz leave an comment :slight_smile:

//esoneson

image of the props from step6 (don’t include the NEW Controller prop):

====================================

Info about the Controller property:


When the UV scroller is running it will look for the “Controller” prop in the object, if it don’t find it it will look in the parent (if existing) of the object. if the prop isn’t found in the object but found in the parent, it will be the prop of the parent that determent if the uv scroll will run or not. This is useful if multiply uv-scrolls need to be disabled at the same time.

Hey, could you take a crack at 2.5d? (sprites that are pinned to a physics hit box and track to camera and change animation sheets based on the physics objects angle compared to camera)

I need to learn bpy, so I can make a sprite sheet generator to run to record 3d models
animations into sprite sheet strips,

I wish there was a universal format for 2.5D…

Hi BluePrintRandom

I included an function named SetPosition (I didn’t include it in the guide as it ain’t tested yet). Calling this function will let you change frame (position of th uv) to an specified by an X and Y coordinate (coordinates of the pre-made frames). I think that you easily, using this function, could write an extension to the script that sets the image based on where the player are located. as it is 2.5 you could base the image on only 1-axie of the player.

Can’t you just render out a certain number of frames and stitch them together? I have used this to create all of my animated textures I use.

Awesome addon though, I will definitely look into using this sometime! :smiley:

I use PIL for stitching together sprite sheets or montages of textures.
PIL is not really updated any more but I think there’s a more modern fork if you look in to it.

well, the idea about the bpy based generator, is to be able to make a actor,
set how what type of angles, you need, (vertical angle slices, horizontal slices, and how many frames the action is) hit go, and it builds the sheet.

and a common system that automatically plays that action based on camera angle + frame?

this could be a object included and supported in blender, with export abilities including only choosing a certain angle etc.

like you could also use frames 1-10 for action 1

frames 11 - 35 =action 2 etc.

so camera angles *frames = sheet size/1024x1024 etc

it would be very useful…

basically a 2.5d sprite generator

a 2.5 d game object type

a 2.5 action actuator if that makes sense?

are there 2d sprite shaders as well for lighting?

it would greatly exand bge usage if it were easy to use and a official build or add on…

Liked it <3

Awesome! Thank you so much Esoneson!

Is it to much if I ask you to comment your script a little bit?

Greetings,

Ortiz

no problem, glad u liked it and thx for the comment.

When I find time for it, I will commit an updated version of this script that contains some comments/optimization/bugfixes and more easy-to-use functions (as an enable/disable function using an optional prop)
hopefully I will get time to do that whitin the next 2-3 days.

Great! Take your time. :slight_smile:
I will follow this thread with great interest.

Version 1.1 released - (see the first post)

News in V 1.1:
*bug fixes
*basic comments in the code (ask me if there are any questions)
*real time invert checking
*new option to turn on/off the uv-scroll by property
*over all improvements

Got an bug reported:

  • using scene.addObject on an UV scrolling object, all newly created objects will use the SAME UV map, this is resulting in uv scrolls that gets “speed up’s” and other strange behaviors (as all objects are using the same uv and modifies it multiply times).

I have found no way to create an new UV layout for every new object. I have however come up whit an solution that only operates once on every uniqe UV layout. as this solution is introducing some limits that I’ll prefer not to have I won’t update the script until I’m Suer that there is no other way to solve this problem.