I’m doing a low poly art style game, and theres two situations where I want to use an animated texture on a model
One situation is doing a simple water texture in a fountain. I’ve found plenty of little demo video clips on Youtube showing people who have accomplished this effect with no explanation of how, and those who have done tutorials by simply shifting a seamless normal map across a plane, but I’ve made a seamless animation with a ripple effect in Gimp that I want to use on a simple plane
The other is a bit more advanced. Since the game is low poly, I want to use textures for faces rather than geometry, something akin to this
The only video I saw showing how to do this used some insanely complicated solution that required dozens of different materials for 1 character and 1 animation which feels completely insane and brute forced.
How would I approach this? I am presently much more focused on the water problem right now than I am the character problem, as thus far I’ve only made 1 character and stuff like dialog will be down the road still, but I would like to know how to do it for later
Another caveat: I am using an older version of Unity, because I’ve been working on this same project for many, many years. So that means I don’t have access to the Shader Graph or anything like that, I’m still in PBR Standard Shader land. But they’ve been accomplishing this effect since the late 90s, so I assume it can be done with a slightly older version of Unity. I know for example you can animate particles using textures, and obviously 2D sprites, so it must be possible.
I would suggest getting acquainted with ShaderLab for both of these It would be great to see a sample of the water effect you want to create. So I will not talk about that for now.
For speaking characters If you want the simple effect in the video I can suggest 2 different way;
Do it in geometry;
Have vertices around character’s mouth and make them move with shape keys or with bones. I think If you do it right you can have an effect pretty similar to this. And this solution isn’t too complex or convoluted. But doesn’t work for eyes well.
Do it with textures;
Yeah this is the complex and convoluted solution you were talking about the good thing about this is you will have more freedom to what to do and your results would be closer to video’s.
I can suggest you doing a simple shader for the head with ShaderLab for this. You can add the mouth and eye textures on top of your faceless texture and I imagine it would make things a little less convoluted. But yeah I wouldn’t be excited about this solution either…
It’s supposed to be a little blurry, but the video makes it look way blurrier than it’s supposed to
So I think I tried tinkering with Shaderlab ages ago for a blur effect for some of my UI and…it kinda worked? I’m definitely out of my comfort zone when it comes to that and what is possible, but it seems to be the best option, make it from scratch
What would be ideal would be if I could make a shader that basically allowed me to replace the regular albedo and Normal Map textures with a sprite sheet version from the 2D system, so I could use the 2D sprite tools to handle animating the 2D stuff, while also being able to take advantage of the 3D aspects like the Normal map and specularity and so on.
I haven’t done something like this before but yeah I think you can do it with a couple of methods. Probably easiest way and what you are asking if possible is UV manipulation and I think you can do this with a script too.
Making face a different material for this would make things much easier for you.
Oh man, this tutorial is a blast from the past, that must be Unity 3, he’s even still running Skype, ha ha. I kinda love it
The only problem: the site he linked to doesn’t exist anymore, so I had to painstakingly copy the javascript from the video by hand, and will have to figure out how to translate it to modern C#
var uvAnimatedTileX = 24;
var uvAnimationTileY = 1;
var framesPerSecond = 10;
function Update() {
var index : int = time.time * framesPerSecond;
index = index % uvAnimationTileX ^ uvAnimationTileY;
var size = Vector2 (1.0 / uvAnimationTileX, 1.0 / uvAnimationTileY);
var uIndex = index % uvAnimationTileX;
var vIndex = index / uvAnimationTileX;
var offset = Vector2 (uIndex * size.x, 1.0 - size.y - vIndex * size.y);
renderer.material.SetTextureOffset ("_MainTex", offset);
renderer.material.SetTextureScale ("_MainTex", size);
}
I’ll just leave this here for future generations…
The trouble I have now is I don’t know jack about Javascript, I’ve only ever used C#, and I’m not totally sure how to translate this. If I could get help with that, that would be great
I assume because this is supposed to be a C# script that this isn’t a shader, and that would be separate?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TextureAnimator : MonoBehaviour
{
public List<Texture2D> Textures;
public Renderer TargetRenderer;
[Range(0, 24)] public int Speed;
public float currentIndex;
private void Update()
{
currentIndex += Speed * Time.deltaTime;
var i = (int)currentIndex;
if (i > Textures.Count - 1)
{
currentIndex = 0;
i = 0;
}
// _BaseMap for URP Lit _MainTex for built in RP
TargetRenderer.material.SetTexture("_BaseMap", Textures[i]);
}
}
OK, I can understand this solution, but the trouble I have is that I’m not sure this works with a normal map
The thing I like about this UV manipulation solution is that I can use whatever textures I want with it theoretically, so then I could make a normal map that is sliced the same way the diffuse is, and have an animated normal map which is cool
Though, this could serve as a nice basis for the solution to the face problem, if I were able to put these 2D textures over the face section of a model, and just run through different animations like that.
I can guess some things on the other solution, like I assume var just becomes Int above Update, for example
It’s just once we get to the inside of Update that I really get thrown off
I think icauroboros’s solution is same in essance, you just need make some changes in preparation. It needs sprite sheet to be seperated to individual textures. Also you can use it with all kinds of textures including normal maps.
As for old code, I think it is not too hard to make the needed changes. But I didn’t check before posting.
public class TextureRemapper : MonoBehaviour
{
public int uvAnimationTileX = 24;
public int uvAnimationTileY = 1;
public float framesPerSecond = 10;
public Renderer rend;
int index;
int uIndex;
int vIndex;
Vector2 size;
Vector2 offset;
void Update()
{
index = Mathf.FloorToInt(Time.realtimeSinceStartup * framesPerSecond);
index = index % (uvAnimationTileX * uvAnimationTileY);
size = new(1f / uvAnimationTileX, 1f / uvAnimationTileY); //??
uIndex = index % uvAnimationTileX;
vIndex = index / uvAnimationTileX;
offset = new(uIndex * size.x, 1f - size.y - vIndex * size.y);
rend.material.SetTextureOffset("_MainTex", offset);
rend.material.SetTextureScale("_MainTex", size); //??
}
}
To find name of textures you can open materials with notepad.
So I’ve been doing battle with this for the last couple of days, and after remaking my texture from scratch a couple of times, rearranging the order of the frames on the texture back and forth trying to figure out the order the frames are supposed to run in according to this code, it…kinda works?
The main thing is I don’t feel like I fully understand what the code is supposed to be doing.
The thing throwing me off is the percent sign, I can’t recall if I’ve ever seen it used as an operator like that, so I have no idea what it’s supposed to be doing
Reading through the code, I can’t tell what order the frames are supposed to be running in. Through experimentation it feels like maybe it is counting vertically from top to bottom, then left to right?
I’ve got it looking…somewhat similar to what it is supposed to look like, but I am finding that it isn’t running at a consistent frame rate. It’s skipping frames, and generally running in a very choppy way. I have it set to run at 10fps, and it most certainly is running much slower most of the time.
I am assuming the first line is what is doing the iterating through the frames, and I am wondering that should be using time.deltaTime somewhere, but I don’t feel nearly confident enough to mess with it, given that I don’t truly know what effect that would have on everything else
The texture itself and the normal map look mostly fine together, and being able to adjust the specularity and smoothness and so on is nice, if I can just get the animation itself to run smoothly we are in business
I am currently side tracked and didn’t check here for a while. I don’t think delta time would do any good here.
Let’s see;
“%” is for modulate, basicly it divides the the number and takes the remainder.
Here It is used for looping through the texture pieces. It is used so that the value always stays in the index value. index = index % (uvAnimationTileX * uvAnimationTileY);
About choppiness if only the frame rate that is inconsistant the part in the down is not best way to do this you may want to use Time.frameCount or some other way to make it more consistent in the lower part. Mathf.FloorToInt (Time.realtimeSinceStartup * framesPerSecond)
Code should pick frames with order first left to right and then go one down and repear but I think part in the down is wrong * should be / like this, probably this is what really makes your consistency problem index = Mathf.FloorToInt(Time.realtimeSinceStartup / framesPerSecond)
I will try to explain and fix things here
public class TextureRemapper : MonoBehaviour
{
public int uvAnimationTileX = 24;
public int uvAnimationTileY = 1;
public float framesPerSecond = 10;
public Renderer rend;
int index;
int uIndex;
int vIndex;
Vector2 size;
Vector2 offset;
void Update()
{
//Calculate index number to go through tiles
index = Mathf.FloorToInt(Time.frameCount / framesPerSecond);
//Divide index number and keep the remainder so it is always in range of tile count
index = index % (uvAnimationTileX * uvAnimationTileY);
//Calculate size of tiles according to number of images on x and y axises.
size = new(1f / uvAnimationTileX, 1f / uvAnimationTileY);
//Which tile to select in x axis; if index goes above X it resets by keeping just the remainder
uIndex = index % uvAnimationTileX;
//Which tile to select in y axis; if x goes above it's row number, the value here increase accordingly to move down to the next row.
vIndex = index / uvAnimationTileX;
//Calculating the size and offset of the tile, which you may think as selecting the correct tile but in calculation.
offset = new(uIndex * size.x, 1f - size.y - vIndex * size.y);
// Set finded offset to material for rendering selected tile
rend.material.SetTextureOffset("_MainTex", offset);
// Set finded size to material for rendering selected tile
rend.material.SetTextureScale("_MainTex", size);
}
}
OK, so I didn’t forget about this, some life stuff just happened, then I got to actually implementing it along with some other visual flare
This commenting really helped me understand what was going on, thank you so much
I did try the fix you added, but it actually kinda broke the FPS counter, so that it worked in reverse and trying to fine tune it was a struggle, so I went back to the original system, but I kept your comments so I could know what was going on later.
After a while of futzing with it, I eventually just settled for what was working, and it actually doesn’t look bad in the current version, with the fountain effect I put over it
It’s not perfect, but it’s definitely passable for this application. (I’m also a little annoyed that the light cookie appears on the surface of the water, not really sure what to do about that)
This solution definitely isn’t workable for doing the animated faces, but right now I’m focused on finishing this prototype scene, theres still some things I need to focus on finishing like most of the second floor of the room, and a few little lighting tweaks, as well as getting dialog boxes running.
Once I have the dialog boxes working though, I’ll be back for figuring out the solution to this face animation thing.
Thank you to those who have helped me so far, and I shall return
It is a little hard for me to make something work well without making any checks. If you can upload the water with it’s mesh, texture, material and script (prefably as a unity package or a zip) I think i can make it work.