2017-08-22 12:57:22 lucah_zhou 阅读数 5667

这一篇文章中,我们将讲解如何在unity3D中通过shader来实现UV动画,来修改纹理Uv坐标以滚动贴图。

先看效果图



UV动画是一种常用的渲染技巧,经常用来描述水的流动、霓虹灯的闪烁等。实现的原理就是动态修改贴图的UV坐标,使物体表面产生变化。采用不同的算法模型可以实现很多很复杂的效果,以下是shader代码:

Shader "Unlit/MyShader/UVAim"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_ScrollY("Scroll Speed", Float) = 1.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100


Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog

#include "UnityCG.cginc"


float _ScrollY;


struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};


struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};


sampler2D _MainTex;
float4 _MainTex_ST;

v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex) + frac(float2(0, _ScrollY) * _Time.y);


UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
Fallback "VertexLit"
}

2018-05-17 18:23:39 salvare 阅读数 2896

流光效果图:
这里写图片描述

演示工程:下载地址

//功能需求:模拟数据传送效果,高亮色块从模型上方移动到下方
//功能分析:这里采用UV动画的方式来实现,利用Alpha贴图控制流动的形状
//          利用Alpha遮罩贴图,控制模型中哪些地方需要进行流动

Shader "Custom/DataFlowEffect"
{
    Properties
    {
        _MainColor("Main Color",Color) = (1,1,1,1)
        _MainTex("Main Texture",2D) = "white"{}
        _Specular("Specular",Color) = (1,1,1,1)
        _Gloss("Gloss",Range(0,255)) = 20.0
        _FlowTex("Flow Tex (A)",2D) = "black"{}
        _FlowColor("Flow Color (RGBA)",Color)=(1,1,1,1)
        _FlowIdleTime("FlowInternal",Range(0,10))=1.0
        _FlowDuring("FlowDuring",Range(0,10))=1.0
        _FlowMaskTex("FlowMasking (A)",2D)="white"{}
        _FlowDirection("FlowDirection",Int)= 0
        _FlowBeginTime("Flow Begin Time",Float)=0
    }

    SubShader
    {
        Tags{"RenderType" = "Opaque" "Queue"="Geometry"}

        Pass
        {
            Tags{"LightMode"="ForwardBase"}
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            sampler2D _MainTex;     //颜色贴图
            half4 _MainTex_ST;      //颜色UV 缩放和偏移
            fixed3 _MainColor;      //漫反射颜色
            fixed3 _Specular;       //高光颜色
            fixed _Gloss;           //高光度
            sampler2D _FlowTex;     //数据流图片
            fixed4 _FlowColor;      //数据流颜色叠加
            half4 _FlowTex_ST;      //数据流贴图UV的缩放和偏移
            fixed _FlowIdleTime;    //流动动画间歇时间
            fixed _FlowDuring;      //流动动画播放时间
            sampler2D _FlowMaskTex; //流动遮罩
            fixed _FlowDirection;   //流动方向
            float _FlowBeginTime;   //流动效果开始的时间

            struct a2v
            {
                half4 pos: POSITION;
                half3 normal :NORMAL;
                half4 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                half4 position : SV_POSITION;
                half2 uv : TEXCOORD0;
                half3 worldNormal : TEXCOORD1;
                half3 worldPos : TEXCOORD2;
                half2 flowUV : TEXCOORD3;
            };

            v2f vert(a2v i)
            {
                v2f v;
                v.position = UnityObjectToClipPos(i.pos);
                v.uv = i.texcoord * _MainTex_ST.xy + _MainTex_ST.zw;
                v.worldNormal = mul(unity_ObjectToWorld,i.normal);
                v.worldPos = mul(unity_ObjectToWorld,i.pos);
                v.flowUV = i.texcoord * _FlowTex_ST.xy + _FlowTex_ST.zw;
                return v;
            }

            //uv - vert的uv坐标
            //scale - 贴图缩放
            //idleTime - 每次循环开始后多长时间,开始流动
            //loopTime - 单次流动时间
            fixed4 getFlowColor(half2 uv,int scale,fixed idleTime,fixed loopTime)
            {
                //当前运行时间
                half flowTime_ = _Time.y - _FlowBeginTime;

                //上一次循环开始,到本次循环开始的时间间隔
                half internal = idleTime + loopTime;

                //当前循环执行时间
                half curLoopTime = fmod(flowTime_,internal);

                //每次开始流动之前,有个停止间隔,检测是否可以流动了
                if(curLoopTime > idleTime)
                {
                    //已经流动时间
                    half actionTime = curLoopTime - idleTime;

                    //流动进度百分比
                    half actionPercentage = actionTime / loopTime;

                    half length = 1.0 / scale;

                    //从下往上流动
                    //计算方式:设:y = ax + b,其中y为下边界值,x为流动进度
                    //根据我们要求可以,x=0时y=-length;x=1时y=1;带入解方程
                    half bottomBorder = actionPercentage * (1+length) - length;
                    half topBorder = bottomBorder + length;

                    //从上往下流动
                    //求解方法与上面类似
                    if(_FlowDirection < 0)
                    {
                        topBorder = (-1-length) * actionPercentage + 1 + length;
                        bottomBorder = topBorder - length;
                    }

                    if(uv.y < topBorder && uv.y > bottomBorder)
                    {
                        half y = (uv.y - bottomBorder) / length;
                        return tex2D(_FlowTex,fixed2(uv.x,y)); 
                    }
                }

                return fixed4(1,1,1,0);
            }

            fixed4 frag(v2f v):SV_Target
            {
                //计算漫反射系数
                fixed3 albedo = tex2D(_MainTex,v.uv) * _MainColor;

                //计算环境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;


                fixed3 worldNormal = normalize(v.worldNormal);                              //世界坐标的法线方向
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(v.worldPos));      //世界坐标的光照方向
                fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(v.worldPos));        //世界坐标的视角方向

                //计算漫反射颜色,采用Half-Lambert模型
                fixed3 lightColor = _LightColor0.rgb;
                fixed3 diffuse = lightColor * albedo * max(0,0.5*dot(worldNormal,worldLightDir)+0.5);

                //计算高光,采用Blinn-Phone高光模型
                fixed3 halfDir = normalize(worldViewDir + worldLightDir);
                fixed3 specColor = _Specular * lightColor * pow(max(0,dot(worldNormal,halfDir)),_Gloss);

                //叠加流动贴图                
                fixed4 flowColor = getFlowColor(v.uv,_FlowTex_ST.y,_FlowIdleTime,_FlowDuring);  
                fixed4 flowMaskColor = tex2D(_FlowMaskTex,v.uv);

                //与遮罩贴图进行混合,只显示遮罩贴图不透明的部分
                flowColor.a = flowMaskColor.a * flowColor.a * _FlowColor.a;

                fixed3 finalDiffuse = lerp(diffuse,_FlowColor,flowColor.a);

                return fixed4(ambient + finalDiffuse+specColor,1);
            }

            ENDCG
        }
    }
    Fallback "Diffuse"
}
2016-08-31 22:33:09 qq_25722767 阅读数 1652


1、前言


本文围绕着实现粒子放大效果,着重讲解android中涉及到动画缩放以及动画集的使用,并且会将讲解一些插值器相关的知识。阅读本文需要读者有一定的自定义View的基础知识,本文将不再讲解自定义View的相关知识,读者需要可以自行去学习,也可以阅读笔者的文章,自定义View的基本知识


2、着色器


为了让效果的色彩比较的绚丽,需要让粒子(这里其实就是用小圆点代替)有一个色彩的过渡,所以需要用到着色器。比较常用到的主要有线性渐变的着色器,它可以让粒子整体上看上去具有一个按照线性排列的色彩过渡。了解线性渐变的着色器之前,先了解shader。在paint里面有一个setshader方法,用于设置着色器,着色器的作用就是用于给颜色实现一个跨度的平衡感,如果paint里面包含了shader对象,那么用此paint绘画的线,圆圈,图像之类的图案,都会从shader里面获取颜色,因此,它是实现绚丽色彩的基础。


2.1LinearGradient

笔者更倾向与叫它线性画笔,它是shader的子类,他实现的颜色平衡感是通过线性渐变来实现的。使用它,很简单,只需要知道它的构造函数就行。


public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
            TileMode tile)

其中x0表示起始点x坐标,x1表示终止点x坐标,color0是起始颜色,color1是终止颜色,tile是平铺模式,它主要有三个值分别是TileMode.CLAMP,TileMode.REPEAT,TileMode.MIRROR。分表表示复制模式,重复模式,镜像模式。假如有如下代码:

 LinearGradient linearGradient=new LinearGradient(0,0,canvas.getWidth(),canvas.getHeight(), Color.RED,Color.BLUE, Shader.TileMode.REPEAT);
        Paint paint=new Paint();
        paint.setShader(linearGradient);
        canvas.drawCircle(canvas.getWidth()/2,canvas.getHeight()/2,canvas.getWidth()/2,paint);


那么它的效果如下:




可以看到线性渐变画笔可以让颜色有一个过渡,这种感觉是很美妙的。如果我们再加一些动画,一些透明度变化,就可以做出很绚丽的色彩效果了。



3.animator


animator是android3.0之后提供的特性,在这之前使用的是animation,区别在于,前者是可以真正改变属性值的,后者只是改变了视图的位置,但是视图的属性并没有得到任何改变,类型障眼法。animator是一个抽象类,是valueAnimator,objectAnimator等的父类,它主要鉴定了一些属性动画的基本操作,比如启动,暂停设置动画监听器等等。


启动动画:

public void start()

 public void end()

上述方法分别用于启动动画和终止动画,start方法由哪个线程启动则运行在哪个线程,end方法则用于终止动画的运行,它会让动画停止并且迅速到结尾的值(属性动画一般会有插值器,默认的是先加速后减速的插值器,用于从一个初始值到结尾制的过渡)。


暂停,继续动画:

public void pause()
public void resume() 

pause用于暂停动画,如果动画尚未start,或者已经end,则此调用会被忽略。注意此方法必须和start同一线程被调用。如果要继续动画,则调用resume方法。


设置动画时长:

 public abstract Animator setDuration(long duration)

用于给动画从开始到结束设置一个时间长度,单位是毫秒。

设置时间插值器:

public abstract void setInterpolator(TimeInterpolator value);

此方法可以设置时间插值器,它的作用是,可以让动画不处于线性变化的效果,默认的话是先加速后减速的插值器。即动画效果从起始值到终止值得过渡,会经历一个加速减速的过程。


设置监听器:

public void addListener(AnimatorListener listener) 

如果希望监听动画是否完成结束或者重复的动作,就可以设置一个动画监听器。animatorListener的原型如下:


3.1 animatorListener


  public static interface AnimatorListener {
      
        void onAnimationStart(Animator animation);

    
        void onAnimationEnd(Animator animation);

    
        void onAnimationCancel(Animator animation);

      
        void onAnimationRepeat(Animator animation);
    }


当调用start时候,onAnimationStart就会被回调,当end被调用的时候,onAnimationEnd被回调。当调用cancel停止动画的时候,onAnimatonCancel被回调。

以上是animator的基本要点。有了这些基本知识,现在就可以学习ValueAnimator了。


4.valueAnimator


valueAnimator是一个值动画,怎么理解呢?它的作用在于,能够把你设置的初始值,和终止值作为起始点,然后通过插值器,在一段时间内,按照插值器的的计算来设置当前动画的值,因为这些值一般都会影响动画的效果,所以叫做值动画。对于插值器,可能大家比较模糊,插值器主要是用来加速度来改变值的,比如:

AccelerateDecelerateInterpolator


 public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }

这是一个先加速后减速的插值器,它的计算过程是通过getInterpolation来计算返回值的。再细心一点,大家可以研究一下cos函数的数值变化,就知道为什么是先加速后减速了。


4.1常用的初始化valueAnimator的方法

既然valueAnimator是值动画,那么自然包含可以设置值的方法,valueAnimator对一些常用的数值类型提供了支持。比如:


public static ValueAnimator ofInt(int... values) 

public static ValueAnimator ofArgb(int... values)

public static ValueAnimator ofFloat(float... values)

它们都可用于设置多个值,注意,千万不要只设置一个值,最少都要两个值,一个代表起始值,一个代表终止值。这三个方法分表表示int数据类型的变化值动画,float,argb颜色的值动画。到这里大家可能迷惑,这些值是除了作为起始点和终止点之外,有什么用处。在valueAnimator中,有一个方法是用于获取属性值的,如下:

 public Object getAnimatedValue()

这个方法用于获取动画当前处于的值,这个值必定是介于起始值和终止值之间的。比如我们用ofInt获得了一个valueAnimator,然后监听动画,在每次动画更新的时候,就可以调用getAnimatedValue方法获取当前的int值,这个值可以用来重新设置画笔的粗细,或者圆的半径,矩形的宽和高......



除了这些数据类型之外,也允许自定义数据类型,但是因为是自定义的类型,valueAnimator并不知道你所需要的变化规则是什么,所以你需要给他们提供变化规则。使用自定义数据类型获取valueAnimator的方法如下:

 public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)

在讲述这个方法之前,先了解一下TypeEvaluator,从字面值上看,它是用来评估值的。它确实也是这样的。


4.2TypeEvaluator


TypeEvaluator是一个接口,用于开发者自定义值变化的规则,可通过ValueAnimator.setEvaluator方法给值动画设置自定义的值变化规则。在这个接口中,最重要也是为一个的一个方法是:

 public T evaluate(float fraction, T startValue, T endValue);

其中fraction是一个插值器提供的值,就是我们前面讲的插值器,通常我们实现这个方法,用result=x0+t*(x1-x0)这个规则就好,x0表示startValue,x1表示endValue,t表示fraction。这样就可以通过getAnimatedValue方法获取到计算结果。


4.3 oFObject方法


现在回到我们刚刚那个方法来。说到,这个方法可以获取一个自定义值变化规则的valueAnimator动画。那么我们需要怎么使用它呢。给出一个例子:

首先需要一个TypeEvaluator提供变化规则。假设现在有一个Circle实体,里面含有x,y,radius分别表示中心点和半径。我们定义一个circle规则的TypeEvaluator

public class CircleEvaluate implements TypeEvaluator<Circle> {
    /**
     * 估算值的表达式 result = x0 + t * (x1 - x0)
     * x0是startValue,x1是endValue,t是fraction
     *
     * @param fraction
     * @param startValue
     * @param endValue
     * @return
     */
    @Override
    public Circle evaluate(float fraction, Circle startValue, Circle endValue) {
        float circleX = startValue.getCenterX() + fraction * (endValue.getCenterX() - startValue.getCenterX());
        float circleY = startValue.getCenterY() + fraction * (endValue.getCenterY() - startValue.getCenterY());
        float radius = startValue.getRadius() + fraction * (endValue.getRadius() - startValue.getRadius());
        return new Circle(circleX, circleY, radius);
    }


接着,就可以使用它定义我们的valueAnimator了。如下:

 ValueAnimator animator = ValueAnimator.ofObject(evaluate, circleGroup[i][j],minCircleGroup[i][j]);

后面两个其实是circle对象。对于valueAnimator,因为是animator的子类,所以包含有基本的start,end等方法,它也可以设置插值器setInterpolator,设置Evaluator.......


5、监听valueAnimator的变更

在valueAnimator里面,我们必须得知道当前的值随着插值器和typeEvaluator的估值,处于什么养的一个状态,所以我们必须监听值得变化,因为值得变化一定伴随着动画的变化,不然怎么叫值动画呢?所以我们需要监听动画的变化来获取估值,然后刷新View重新根据新的值来绘制界面。

 public static interface AnimatorUpdateListener {
        void onAnimationUpdate(ValueAnimator animation);

    }


只要我们监听了这个对象,那么就可以从animation.getAnimatedValue获取当前的估值。


大部分时候,仅仅对一个对象进行动画,并不能满足我们的需求,我们很经常需要对很多对象进行动画的变化,这就涉及到集体的变换了。在android里面,提供了AnimatorSet,来解决这个问题。


6、animatorSet


animatorSet是一个动画集合,里面的动画都保存在一个列表里面,它们可以一起播放,也可以按次序播放,或者指定一定的时间间隔播放。总的来说,对它的操作就相当于对集合中所有的动画的操作。比如调用animatorSet的start方法,等同于调用动画集合所有的动画的start方法。其中调用的次序是可以设置的。知道了这些,下面我们详细了解一下这个类带给了我们什么。


6.1设置动画集合的播放规则


这个播放规则不是动画的播放规则(TypeEvaluator和interpolarter决定),而是动画间的播放规则。主要有

public void playTogether(Animator... items)
public void playTogether(Collection<Animator> items)
public void playSequentially(Animator... items)
public void playSequentially(List<Animator> items) 

总共提供了两种规则,一种是所有的动画一起播放,另外一种是等前面一个播放完毕再接着播放下一个。


前面提到AnimatorSet只是一个动画的集合,因此,Animator有的它都有,这里就不再说了。



7、使用Animator实现粒子缩放效果

接下来,通过实例讲解animator的使用。例子实现了一个同时对圆点集合进行缩放的效果,在缩放过程中,通过改变圆点的中心坐标和半径,来改变圆点的位置和大小,从而达到粒子缩放效果。


首先定义一个attrs.xml文件。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ParticleView">
        <attr name="circleStartColor" format="color"></attr>
        <attr name="circleEndColor" format="color"></attr>
        <attr name="row" format="integer"></attr>
        <attr name="col" format="integer"></attr>
    </declare-styleable>
</resources>


然后定义小圆点的实体:

package cn.com.chinaweal.mypartical;

/**
 * Created by Myy on 2016/8/31.
 */
public class Circle {

    private float centerX;
    private float centerY;
    private float radius;

    public Circle(float centerX, float centerY, float radius) {
        this.centerX = centerX;
        this.centerY = centerY;
        this.radius = radius;
    }

    public float getCenterX() {
        return centerX;
    }

    public void setCenterX(float centerX) {
        this.centerX = centerX;
    }

    public float getCenterY() {
        return centerY;
    }

    public void setCenterY(float centerY) {
        this.centerY = centerY;
    }

    public float getRadius() {
        return radius;
    }

    public void setRadius(float radius) {
        this.radius = radius;
    }
}

设置圆点的变化规律:


package cn.com.chinaweal.mypartical;

import android.animation.TypeEvaluator;

/**
 * 圆点的属性变化评估值,用于动画过程获取值
 * Created by Myy on 2016/8/31.
 */
public class CircleEvaluate implements TypeEvaluator<Circle> {
    /**
     * 估算值的表达式 result = x0 + t * (x1 - x0)
     * x0是startValue,x1是endValue,t是fraction
     *
     * @param fraction
     * @param startValue
     * @param endValue
     * @return
     */
    @Override
    public Circle evaluate(float fraction, Circle startValue, Circle endValue) {
        float circleX = startValue.getCenterX() + fraction * (endValue.getCenterX() - startValue.getCenterX());
        float circleY = startValue.getCenterY() + fraction * (endValue.getCenterY() - startValue.getCenterY());
        float radius = startValue.getRadius() + fraction * (endValue.getRadius() - startValue.getRadius());
        return new Circle(circleX, circleY, radius);
    }
}


接着自定义View实现可缩放的粒子效果:

package cn.com.chinaweal.mypartical;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.WindowManager;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Myy on 2016/8/31.
 */
public class ParticleView extends View {

    private Paint circlePaint;//小圆点画笔
    private Context context;
    private int circleStartColor, circleEndColor;//小圆点起始颜色,终止颜色
    private int row = 10, col = 10;
    private Circle circleGroup[][],minCircleGroup[][];//圆点数组,最小圆点数组,这个可以用来在圆点动画过程中指定最终值
    private float startWidth, startHeight, circleWidth, circleHeight,minCircleBound;
    private float circlePadding;//圆点的间距
    private float density;//像素密度
    private float radius;//圆点半径
    private float firstCircleX, firstCircleY;//第一个小圆点的中心坐标
    private final static int START=0,ANIMATION_CIRCLE=1;//初始窗台,圆点动画状态
    private int status=START;

    public ParticleView(Context context) {
        super(context);
        init(context);
    }

    public ParticleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.ParticleView);
        circleStartColor = typeArray.getColor(R.styleable.ParticleView_circleStartColor, Color.CYAN);
        circleEndColor = typeArray.getColor(R.styleable.ParticleView_circleEndColor, Color.RED);
        row = typeArray.getInt(R.styleable.ParticleView_row, 10);
        col = typeArray.getInt(R.styleable.ParticleView_col, 10);

        init(context);
    }

    private void init(Context context) {
        this.context=context;
        circlePaint = new Paint();
        circlePaint.setColor(circleStartColor);
        circleGroup = new Circle[row][col];
        minCircleGroup=new Circle[row][col];
        getDensity();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(status==START) {
            initCanvas(canvas);
        }
        if(status==ANIMATION_CIRCLE)
        {
            for (int i = 0; i < row; i++) {
                for (int j = 0; j < col; j++) {
                    canvas.drawCircle(circleGroup[i][j].getCenterX(), circleGroup[i][j].getCenterY(), circleGroup[i][j].getRadius(), circlePaint);
                }
            }
        }
    }

    /**
     * 初始化布局
     * @param canvas
     */
    private void initCanvas(Canvas canvas) {
        startWidth = canvas.getWidth() - getPaddingLeft() - getPaddingRight();
        startHeight = canvas.getHeight() - getPaddingTop() - getPaddingBottom();
        circlePadding = 5 * density;
        circleWidth = (startWidth - circlePadding * (col - 1)) / col;
        circleHeight = (startHeight - circlePadding * (row - 1)) / row;
        minCircleBound=Math.min(circleWidth,circleHeight);//以最小的值为直径,这样才可以完全显示所有的圆点
        radius = minCircleBound / 2;//获取半径
        firstCircleX = minCircleBound / 2;
        firstCircleY = circleHeight / 2;
        //设置渐变画笔效果
        LinearGradient linearGradient = new LinearGradient(0, 0, startWidth, startHeight, circleStartColor, circleEndColor, Shader.TileMode.MIRROR);
        circlePaint.setShader(linearGradient);
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                circleGroup[i][j] = new Circle(firstCircleX + (minCircleBound + circlePadding) * i, firstCircleY + (minCircleBound + circlePadding) * j, radius);
                minCircleGroup[i][j] = new Circle(firstCircleX + (minCircleBound + circlePadding) * i+circlePadding*3, firstCircleY + (minCircleBound + circlePadding) * j+circlePadding*3, circlePadding);
                canvas.drawCircle(circleGroup[i][j].getCenterX(), circleGroup[i][j].getCenterY(), radius, circlePaint);
            }
        }
    }

    /**
     * 启动动画
     */

    public void startAnimation() {
        //false表示每个动画都是用自己的插值器否则就是用共有的插值器
        status=ANIMATION_CIRCLE;
        final AnimatorSet animatorSet = new AnimatorSet();
        List<Animator> animatorList = new ArrayList<>();
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                //valueAnimator可以设置动画属性的值在某个区间内以某种方式进行加速或者减速
                final int tempRow=i;
                final int tempCol=j;
                final CircleEvaluate evaluate=new CircleEvaluate();
                ValueAnimator animator = ValueAnimator.ofObject(evaluate, circleGroup[i][j],minCircleGroup[i][j]);
                animator.setDuration(3000);
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        Circle circle=(Circle)animation.getAnimatedValue();
                        circleGroup[tempRow][tempCol]=circle;
                        invalidate();
                    }
                });
                animatorList.add(animator);
            }
        }

        animatorSet.playTogether(animatorList);
        animatorSet.start();
        animatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

    }

    /**
     * 获取屏幕密度
     */
    private void getDensity() {
        DisplayMetrics displayMetrics = new DisplayMetrics();
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
        density = displayMetrics.density;
    }

}

读者在看这个例子的时候,关注点不要在哪些半径直径的东西,而是关注valueAnimator和circle之间的变化,以及这些变化如何引起view渲染的。

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:weightSum="2"
    android:orientation="vertical"
    tools:context="cn.com.chinaweal.mypartical.MainActivity">

    <cn.com.chinaweal.mypartical.ParticleView
        android:id="@+id/particleView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        app:circleStartColor="#efefaa"
        app:circleEndColor="#aa11aa"
        android:background="#efefef"
        app:col="8"
        app:row="8" />
    <cn.com.chinaweal.mypartical.ParticleView
        android:id="@+id/particleView1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        app:circleStartColor="#eaaefa"
        app:circleEndColor="#1133aa"
        app:col="10"
        app:row="10" />
</LinearLayout>

acitivtiy:


package cn.com.chinaweal.mypartical;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    private ParticleView particleView;
    private ParticleView particleView1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.shader_layout);
        particleView = (ParticleView) findViewById(R.id.particleView);
        particleView1=(ParticleView)findViewById(R.id.particleView1);
    }

    @Override
    protected void onStart() {
        super.onStart();
        particleView.postDelayed(new Runnable() {
            @Override
            public void run() {
                particleView.startAnimation();
                particleView1.startAnimation();
            }
        }, 1000);
    }
}

效果图:


开始:



进过一段时间:





如果读者了解了,可以继续研究文字的拖拽,矩形的变化,图片的旋转.......各种效果,综合起来就可以达到很炫的动画,可以用作起始页或者引导页作为欢迎动画......



---------文章写自:HyHarden---------

--------博客地址:http://blog.csdn.net/qq_25722767-----------


2015-01-31 19:05:00 weixin_30813225 阅读数 8

不知道大家有没有玩过赛车游戏

赛车游戏的跑道有路标,如下图

玩过赛车游戏的都知道,大多数赛车游戏的路标是会动的,如上图,它会从右往左运动

不会发动态图,大家脑补一下吧

没有玩过赛车游戏的也不要紧,大家见过游戏中的瀑布或者湖面吗?如下图:高山流水

 

当然,我这个贴图的效果不是很好,不过我这里要说的是原理,不妨碍,我们继续

图中的瀑布从上往下流动

我们都知道,如果用粒子的话,有几百个瀑布,在手机上肯定是非常卡的

差一点的手机说不定游戏刚打开就未响应了

除非要求是非常逼真的那种水才用粒子系统,能不用尽量不用

用纹理动画的话虽然达不到粒子那么逼真,但是也是一种不错的解决方案

纹理动画耗费资源还是粒子动画耗费资源呢?我们一探究竟

小二,上图

好勒客官,马上来

 

这是瀑布的材质球,我们手动修改OffsetX

按每次+0.1这样修改:0.1,0.2,0.3,0.4....

我们会发现,每修改一次材质球就会相应的变动

现在我们归零,修改一下OffsetY,依旧和修改X一样的方法

我们会发现材质球也会有相应的变动,但是方向不一样

如果只观察材质球感觉不容易看出来,那就把材质球挂到物体上,这样看的清楚一些

好了,我们知道如果让材质球动起来了,修改offset即可,那么我们就开始敲代码吧

 

创建一个 TextureAnimation.cs

定义一个变量:public float XSpeed = 0.1f;//X轴移动速度

然后有了下面的代码

1      public float XSpeed = 0.1f;//X轴移动速度
2  
3      void FixedUpdate()
4      {
5          renderer.material.mainTextureOffset = new Vector2(Time.time * XSpeed, renderer.material.mainTextureOffset.y);    
6      }

 我们挂到瀑布上,然后运行,我们发现瀑布动起来了,但是似乎移动方向和速度不对劲

没关系,我们刚才修改的是X轴,我们在加上Y轴和移动速度不就行了?

依法炮制得出下面的完整代码:

 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 /// 纹理动画 <summary>
 5 /// 纹理动画
 6 /// </summary>
 7 public class TextureAnimation : MonoBehaviour
 8 {
 9     public bool MoveX = false;//是否移动X
10     public bool ToUp = false;//往上移动
11     public float XSpeed = 0.1f;//X轴移动速度
12     private float offsetX = 0;
13     
14     public bool MoveY = false;//是否移动Y
15     public bool ToLeft = false;//往左移动
16     public float YSpeed = 0.1f;//Y轴移动速度
17     private float offsetY = 0;
18 
19     
20     void FixedUpdate()
21     {
22         if (MoveX)
23         {
24             offsetX = Time.time * XSpeed;
25             if (ToUp) offsetX *= -1;
26         }
27 
28         if (MoveY)
29         {
30             offsetY = Time.time * YSpeed;
31             if (ToLeft) offsetY *= -1;
32         }
33 
34         renderer.material.mainTextureOffset = new Vector2(offsetX, offsetY);    
35     }
36 }

 

我就不多解释了,房东一会儿过来收房租了

真烦,我都租了大半年了,想在续租一个月,因为还有一个月我才上班

原来房租是850一个月,现在居然想收我900一个月

原因是我只续租一个月

我教了一千块押金,他怕我跑了不给水电费,非要我在给一千块

我让他来收电费他也不来,说太冷了。。。。

什么人啊这是。。。

不说了,我先闪了

 

本文链接:http://www.cnblogs.com/shenggege/p/4264463.html

转载于:https://www.cnblogs.com/shenggege/p/4264463.html

2016-11-17 20:10:49 a615538001 阅读数 7319

最近项目有个需求就是模拟油管里的油流动的效果,想想让建模的直接做一个动画是最简单!不过,自己也想用粒子系统来实现一下!初期得到不错的效果!现在的需求就是这里有一根油管,当然不是一根直直的油管,会拐弯,油管里会有油在流动!

找了很多资料也没有找到关于如何让粒子沿着路径运动的,那么自己开始动手吧!

原理很简单,我们都知道粒子系统里有一个Velocity over LifeTime 这样一个属性,里面是可以选择使用Curve来设置粒子生命周期内的速度属性!速度决定粒子的运动轨迹!

1.一开始我们设置粒子的Start Speed 为0,避免对我们设置的速度造成影响;

2.开始调整Velocity over LifeTime的Curve,使粒子沿着我们要求的路径进行运动;

好了,就这两点!不过我相信即使原理简单,但是要手动去调Curve也会让人崩溃,而且还要调X,Y,Z三个方向上的速度曲线!

一开始我是手动调的,明白了调整Curve可以让粒子沿着路径运动是可以做到的,那么下面就开始动手写一个小扩展,用代码帮忙调Curve了,毕竟程序员就是要用程序来简化实际中的很多工作!

首先先写一个运行时动态设置例子系统的Velocity over LifeTime Curve的Monobehaviour吧!

第一步:在场景中放置N个路径点;

第二步:写脚本,脚本大致就是通过场景中的路径点的得到位置,注意路径点不能重叠,否则得不到想要的效果,然后认定其中一个为起点,然后计算方向和距离,并得到每前后两个点的距离和总的路径长度,通过前后两个点之间的距离与总的距离的比得到该点在Curve上X轴的位置,当然第一个点从0开始!然后生成AnimationCurve,有一点需要说明的就是KeyFrame设置里面的inTangent和outTangent,这里设置成正无穷大,就表示是90度角,那么我们得到的Curve会是一根折现,而且Curve所有拐角的地方都是90度角,因此我们得到粒子沿着路径运动实际上是沿着一根折线路径,这也是我这里沿着路径运动的粒子比较受限制的地方!不过你可以再调整好沿着折线运动之后,再去调整Curve的inTangent和outTangent就可以达到沿着曲线运动了,哈哈,当然这个运行时设置Curve的脚本就没有这个功能了,不过后面我写到的一个小扩展就可以做到!先上代码:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;



public class PathParticle : MonoBehaviour
{
    public ParticleSystem Particle;
    public Transform[] wayPoints;

    void Start()
    {
        if (wayPoints.Length > 1)
        {
            Queue<FrameDate> frames;
            float distance = ParticleUtils.CalculateDirection(new List<Transform>(wayPoints), out frames);
            Particle.transform.position = wayPoints[0].transform.position;
            AnimationCurve curve_X;
            AnimationCurve curve_Y;
            AnimationCurve curve_Z;
            float lifeTime = distance / Particle.startLifetime;
            ParticleUtils.MakeCurve(frames, distance, lifeTime, out curve_X, out curve_Y, out curve_Z);
            var vel = Particle.velocityOverLifetime;
            vel.enabled = true;
            vel.space = ParticleSystemSimulationSpace.Local;
            vel.x = new ParticleSystem.MinMaxCurve(lifeTime, curve_X);
            vel.y = new ParticleSystem.MinMaxCurve(lifeTime, curve_Y);
            vel.z = new ParticleSystem.MinMaxCurve(lifeTime, curve_Z);
        }
    }
}


-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

下面是一个主要的工具类:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;


public struct FrameDate
{
    public FrameDate(Vector3 direction, float distance)
    {
        Direction = direction;
        Distance = distance;
    }
    public Vector3 Direction;
    public float Distance;
}

public class ParticleUtils
{
    public static float CalculateDirection(List<Transform> wayPoints, out Queue<FrameDate> frames)
    {
        int size = wayPoints.Count;
        float totalDistance = 0;
        frames = new Queue<FrameDate>();

        for (int i = 1; i < size; ++i)
        {
            Vector3 dir = wayPoints[i].position - wayPoints[i - 1].position;
            float dis = Vector3.Distance(wayPoints[i].position, wayPoints[i - 1].position);
            totalDistance += dis;
            dir.Normalize();
            frames.Enqueue(new FrameDate(dir, dis));
        }
        return totalDistance;
    }

    public static void MakeCurve(Queue<FrameDate> frames,
        float totalDistance,
        float totalTime,
        out AnimationCurve curve_X,
        out AnimationCurve curve_Y,
        out AnimationCurve curve_Z)
    {
        curve_X = new AnimationCurve();
        curve_Y = new AnimationCurve();
        curve_Z = new AnimationCurve();
        float curTime = 0;
        while (frames.Count > 0)
        {
            FrameDate data = frames.Dequeue();
            curve_X.AddKey(new Keyframe(curTime, data.Direction.x, float.PositiveInfinity, float.PositiveInfinity));
            curve_Y.AddKey(new Keyframe(curTime, data.Direction.y, float.PositiveInfinity, float.PositiveInfinity));
            curve_Z.AddKey(new Keyframe(curTime, data.Direction.z, float.PositiveInfinity, float.PositiveInfinity));
            curTime += (data.Distance / totalDistance);
        }
    }
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

到这里,把上面的第一个类绑定到场景中的一个GameObject上面,把ParticleSystem 和相应的路径点拖到属性里面去,然后Play得到如下效果:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

OK,上面的基本就能实现粒子沿着我们设置的路径点形成的折线路径运动了!

下面开始介绍在Editor下,预先生成Curve的小扩展!先上效果图:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

这个小扩展很简单,就是先选择你要生成Curve的ParticleSystem,然后添加路径点,把场景中的路径点Transform拖入到新添加的路径点中,然后Generate Curve,这样就生成沿着设置的折线路径运动啦!Editor下的编程就不用多说了,全部代码如下:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

[ExecuteInEditMode]
public class GizmosDrawer : MonoBehaviour
{
    List<Vector3> nodes = new List<Vector3>();
    [ExecuteInEditMode]
    void OnDrawGizmos()
    {
        int count = nodes.Count;
        for (int i = 1; i < count; ++i)
        {
            Gizmos.DrawLine(nodes[i - 1], nodes[i]);
        }
    }

    public void SetNodes(List<Vector3> nodes)
    {
        this.nodes.Clear();
        this.nodes.AddRange(nodes);
    }
}


-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
public class ParticlePathCurveMaker : EditorWindow
{
    public  class PathNode
    {
        public PathNode(int index)
        {
            Index = index;
            Node = null;
            Remove = false;
            Tip = null;
        }
        public int Index;
        public Transform Node;
        public bool Remove;
        public GameObject Tip;
        public void ShowTip()
        {
            if (Tip)
            {
                Tip.transform.parent = Node;
                Tip.transform.ResetLocal();
                TextMesh textMesh = Tip.GetComponent<TextMesh>();
                textMesh.text = "Node " + Index;
                Collider collider = Node.GetComponent<Collider>();
                if (collider)
                {
                    float h = collider.bounds.size.y;
                    Vector3 temp = Tip.transform.position;
                    temp.y += h;
                    Tip.transform.position = temp;
                }
            }
            else
            {
                GameObject tip = new GameObject("Node_" + Index);
                tip.AddComponent<MeshRenderer>();
                TextMesh textMesh = tip.AddComponent<TextMesh>();
                textMesh.text = "Node " + Index;
                textMesh.anchor = TextAnchor.MiddleCenter;
                textMesh.color = Color.green;
                textMesh.fontSize = 10;
                Tip = tip;
                tip.transform.parent = Node;
                tip.transform.ResetLocal();
            }
        }
    }

    private ParticleSystem psystem;
    private Dictionary<int, PathNode> nodes = new Dictionary<int, PathNode>();
    private int curNodes = 0;
    private bool needResort = false;
    private Vector2 scrollPosition;
    private GizmosDrawer gd = null;

    void OnEnable()
    {
        // Setup the SerializedProperties.
        if (!gd)
        {
            GameObject go = new GameObject("Gizmos");
            gd = go.AddComponent<GizmosDrawer>();
            if (!gd)
            {
                Debug.Log("Fiald");
            }
        }
    }

    void OnDisable()
    {
        foreach (var item in nodes.Values)
        {
            if (item.Tip)
            {
                DestroyImmediate(item.Tip);
            }
        }
        nodes.Clear();
        if (gd)
        {
            DestroyImmediate(gd.gameObject);
            gd = null;
        }
    }

    [MenuItem("ExtensionTools/Open Particle Path Curve Maker")]
    static void Init()
    {
        ParticlePathCurveMaker window = GetWindow<ParticlePathCurveMaker>();
    }

    void OnGUI()
    {
        EditorGUILayout.Space();
        EditorGUILayout.BeginVertical();
        Rect rect = GUILayoutUtility.GetRect(500f, 23f);
        psystem = EditorGUI.ObjectField(rect, "ParticleSystem:", psystem, typeof(ParticleSystem), true) as ParticleSystem;

        if (psystem)
        {
            ParticleSystem.ShapeModule shade = psystem.shape;
            shade.enabled = false;
            psystem.startSpeed = 0;
        }

        EditorGUILayout.Space();
        EditorGUILayout.PrefixLabel("Nodes:");
        List<Vector3> path = new List<Vector3>();
        using (var scrollViewScope = new EditorGUILayout.ScrollViewScope(scrollPosition))
        {
            scrollPosition = scrollViewScope.scrollPosition;
            scrollViewScope.handleScrollWheel = true;
            int count = nodes.Count;
            List<PathNode> temp = new List<PathNode>(nodes.Values);
            for (int i = 0; i < count; ++i)
            {
                MakePathNode(temp[i]);
            }
        }

        EditorGUILayout.Space();

        if (needResort)
        {
            ResortNodes();
        }

        List<Vector3> linePos = new List<Vector3>();
        foreach (var item in nodes.Values)
        {
            if (item.Node)
            {
                linePos.Add(item.Node.position);
            }
        }
        if (gd)
        {
            gd.SetNodes(linePos);
        }

        using (var h = new EditorGUILayout.HorizontalScope("Button"))
        {
            if (GUILayout.Button("Add path node"))
            {
                //添加路径点
                nodes.Add(curNodes,new PathNode(curNodes));
                curNodes++;
            }

            if(GUILayout.Button("Generate Curve"))
            {
                if (psystem && nodes.Count > 1)
                {
                    GenerateCurve();
                }
            }
        }

        EditorGUILayout.Space();
        EditorGUILayout.EndVertical();

        this.Repaint();
    }

    void ProgressBar(float value, string label)
    {
        // Get a rect for the progress bar using the same margins as a textfield:
        Rect rect = GUILayoutUtility.GetRect(18, 18, "TextField");
        EditorGUI.ProgressBar(rect, value, label);
        EditorGUILayout.Space();
    }

    //显示路径备选单元格
    void MakePathNode(PathNode node)
    {
        EditorGUILayout.BeginHorizontal();
        Rect rect = GUILayoutUtility.GetRect(500f, 20f);
        node.Node = EditorGUI.ObjectField(rect, string.Format("Node {0}:",node.Index), node.Node, typeof(Transform), true) as Transform;

        if (node.Node)
        {
            node.ShowTip();
        }

        if (GUILayout.Button("Remove"))
        {
            node.Remove = true;
            nodes.Remove(node.Index);
            needResort = true;
            DestroyImmediate(node.Tip);
            node.Tip = null;
            --curNodes;
        }
        EditorGUILayout.EndHorizontal();
    }
    //重新排序路径点
    void ResortNodes()
    {
        List<PathNode> temp = new List<PathNode>(nodes.Values);
        temp.Sort((a, b) =>
        {
            return a.Index.CompareTo(b.Index);
        });
        nodes.Clear();
        for(var i=0; i < temp.Count; ++ i)
        {
            PathNode node = temp[i];
            node.Index = i;
            nodes.Add(i, node);
        }
    }

    void GenerateCurve()
    {
        Queue<FrameDate> frames;
        List<Transform> wayPoints = new List<Transform>();
        List<PathNode> pathnodes = new List<PathNode>(nodes.Values);

        pathnodes.Sort((a, b) =>
        {
            return a.Index.CompareTo(b.Index);
        });

        int count = pathnodes.Count;
        for(int i=0; i< count; ++i)
        {
            if (i == 0)
            {
                psystem.transform.position = pathnodes[i].Node.position;
                psystem.transform.eulerAngles = Vector3.zero;
            }
            if (pathnodes[i].Node)
            {
                wayPoints.Add(pathnodes[i].Node);
            }
        }
        //至少要两个点
        if(wayPoints.Count > 1)
        {
            float distance = ParticleUtils.CalculateDirection(wayPoints, out frames);
            AnimationCurve curve_X;
            AnimationCurve curve_Y;
            AnimationCurve curve_Z;
            float lifeTime = distance / psystem.startLifetime;
            ParticleUtils.MakeCurve(frames, distance, lifeTime, out curve_X, out curve_Y, out curve_Z);
            var vel = psystem.velocityOverLifetime;
            vel.enabled = true;
            vel.space = ParticleSystemSimulationSpace.Local;
            vel.x = new ParticleSystem.MinMaxCurve(lifeTime, curve_X);
            vel.y = new ParticleSystem.MinMaxCurve(lifeTime, curve_Y);
            vel.z = new ParticleSystem.MinMaxCurve(lifeTime, curve_Z);
        }

    }
}

#endif



潮湿效果之水纹

阅读数 38

没有更多推荐了,返回首页