in Unity serving two view mode game view and scene view
Sceneview is can possibly that control viewport as panning rotate zoom
but Gameview is only offered captured scene by camera

usually, look at Scene view when artist crafting art asset(effect, prefab..)
but some times needs to look at Gameview on working for a perfect match on gameplay

 

on this situation we need some controllable Gameview

that is very easy during gameplay, just manipulate camera's 'transform' from input key action

 

but on editor mode situation, 

 

designers need control able Gameview for their work

 

this script offered some camera moving action for game view
drag mouse on Gameview with pushing shift key is  zoom action, also dragging with control key is panning action

 

no more need this action

select prefab 

working

need view change

select camera

move transform

select effect prefab

working

need view change

select camera..

..

you just dragging on game view with correct key

 

 

 

 

//////////////////////////////////////////  code explane update later!!

we will think about how controll viewport

it's simple, zoom is using  ortho size, panning is moving positions

 

point is how to make it working at editormode?\

we find that answer at unity3d event category

 

 

 

using UnityEngine; 
using UnityEditor; 
using System.Collections; 

[ExecuteInEditMode] 

public class cameraControlTest : MonoBehaviour 


    public Vector3 FMousePosition = new Vector3(0, 0, 0); 
    public Vector3 CMousePosition = new Vector3(0, 0, 0); 
    public Vector3 FMousePosition2 = new Vector3(0, 0, 0); 
    public Vector3 CMousePosition2 = new Vector3(0, 0, 0); 
    private float range = 0.0f; 
    public Vector3 movevector = new Vector3(0, 0, 0); 
    public float panspeed = 1.0f; 
    private float vectorx = 0.0f; 
    private float vectorx2 = 0.0f; 
    public float wheelscale = 0.0f; 

//> vector3 variables are for calculating mouse dragging distance

The reason why all variables are public is that they are under development

also this script is excluded on build version

any variables are  not necessary to be 'public'

 

    void OnGUI() 
    { 

    Event e = Event.current; 
        range = range + Time.deltaTime; 
        if (e.control) //if (e.button == 1) 
//>check pushing 'control key'

(e.control = event.current.control )is checking keyboard event

some key has no reactions, that is don't know why is, so found a workable key of keyboard


        { 
            //---- 마우스 우클릭을 제외한 다른 클릭에대한 오류가 있음(좌클릭은 모든 클릭에 반영 
            if (e.button == 1) //if (e.control) 

//>under blocks code line is calculated when pushing 'controlkey' and pushing mouse button 1(right button) 
            { 
                if (range > 0.1f) 
                { 
                    CMousePosition = e.mousePosition; 
                    float vectorx = FMousePosition.x; 
                    if (vectorx != 0) 
                        {     } 
                    else 
                        FMousePosition = e.mousePosition; 

                range = 0.0f; 


        movevector = new Vector3((CMousePosition - FMousePosition).x, 0f, (CMousePosition - FMousePosition).y); 
        gameObject.transform.localPosition = gameObject.transform.position + movevector * (panspeed*0.001f); 

                } 
            } 
                  
//>all conditions are true is calculate mouse dragging distance and that append to the camera position
         
            else 
            { 
                FMousePosition = new Vector3(0, 0, 0);//마우스 버튼을 떼면 처음 위치는 초기화 
                FMousePosition2 = new Vector3(0, 0, 0);//마우스 버튼을 떼면 처음 위치는 초기화 
            } 

//>this block is reset function

position variables are applied zero when leaving the mouse button 

        } 

 

//> this block is exactly the same as above
//> The difference is that check the shift button


        else if (e.shift) 
            //---- 마우스 우클릭을 제외한 다른 클릭에대한 오류가 있음(좌클릭은 모든 클릭에 반영 
            if (e.button == 1) //if (e.control) 
            { 
               if (range > 0.1f) 
                { 

                    CMousePosition2 = e.mousePosition; 
                    float vectorx2 = FMousePosition2.x; 
                    if (vectorx2 != 0) 
                    {                     } 
                    else 
                        FMousePosition2 = e.mousePosition; 

                    range = 0.0f; 
                    wheelscale = (CMousePosition2 - FMousePosition2).y; 
                    Camera cam = gameObject.GetComponent(); 
                    cam.orthographicSize = cam.orthographicSize + wheelscale * (panspeed * 0.001f); 
                } 
            } 

            else 
            { 
                FMousePosition2 = new Vector3(0, 0, 0);//마우스 버튼을 떼면 처음 위치는 초기화 
            } 
    } 
}

 

 

actually part of the calculation is very easy even not to explain
we need are how to make it on editor mode

 

 

 

////////////////////////////////////////// full code

 

using UnityEngine;
using UnityEditor;
using System.Collections;

[ExecuteInEditMode]



public class cameraControlTest : MonoBehaviour

{
    public Vector3 FMousePosition = new Vector3(0, 0, 0);
    public Vector3 CMousePosition = new Vector3(0, 0, 0);
    public Vector3 FMousePosition2 = new Vector3(0, 0, 0);
    public Vector3 CMousePosition2 = new Vector3(0, 0, 0);
    private float range = 0.0f;
    public Vector3 movevector = new Vector3(0, 0, 0);
    public float panspeed = 1.0f;
    private float vectorx = 0.0f;
    private float vectorx2 = 0.0f;
    public float wheelscale = 0.0f;

    void OnGUI()
    {

    Event e = Event.current;
        range = range + Time.deltaTime;
        if (e.control) //if (e.button == 1)

        {
            //---- 마우스 우클릭을 제외한 다른 클릭에대한 오류가 있음(좌클릭은 모든 클릭에 반영
            if (e.button == 1) //if (e.control)
            {
                Debug.Log("keycontrol");
                
                if (range > 0.1f)
                {
                    
                    CMousePosition = e.mousePosition;
                    //Debug.Log(CMousePosition);
                                        
                    float vectorx = FMousePosition.x;
                
                    if (vectorx != 0)
                        {
                        }
                    else
                        {
                        FMousePosition = e.mousePosition;
                        Debug.Log("Fmouse");
                        }


                range = 0.0f;
                movevector = new Vector3((CMousePosition - FMousePosition).x, 0f, (CMousePosition - FMousePosition).y);

                gameObject.transform.localPosition = gameObject.transform.position + movevector * (panspeed*0.001f);

                }
            }
                 

        
            else
            {
                FMousePosition = new Vector3(0, 0, 0);//마우스 버튼을 떼면 처음 위치는 초기화
                FMousePosition2 = new Vector3(0, 0, 0);//마우스 버튼을 떼면 처음 위치는 초기화
            }
         
        }

        else if (e.shift)
            //---- 마우스 우클릭을 제외한 다른 클릭에대한 오류가 있음(좌클릭은 모든 클릭에 반영
            if (e.button == 1) //if (e.control)
            {
                Debug.Log("keyshift");

                if (range > 0.1f)
                {

                    CMousePosition2 = e.mousePosition;
                    //Debug.Log(CMousePosition);

                    float vectorx2 = FMousePosition2.x;

                    if (vectorx2 != 0)
                    {
                    }
                    else
                    {
                        FMousePosition2 = e.mousePosition;
                        Debug.Log("Fmouse");
                    }

                    range = 0.0f;
                    wheelscale = (CMousePosition2 - FMousePosition2).y;
                    Camera cam = gameObject.GetComponent();
                    cam.orthographicSize = cam.orthographicSize + wheelscale * (panspeed * 0.001f);
                }
            }

            else
            {
                FMousePosition2 = new Vector3(0, 0, 0);//마우스 버튼을 떼면 처음 위치는 초기화
            }
    }
}

in unreal engine default offered laser system, but in unity has no laser system

 

lately, we need laset system,  so decided to  create lower version

 

laset system define

 

1. conect 2 point(player & enemy)

2. possible to move on runtime(game play)

3. able to select system as texture tiling or scaling

 

 

we will creating script look like this image

 

conect point1 to 2

move able on runtime or editormode

 

texture tiling mode

usually,  used in beam style

texture scaling mode

usually,  used in image sequence style

we need material, script, prefab object for creating this system

 

/////////////////////prefab object

I offered default prefab object to 'effect designer'
it had a fixed hierarchy,  don't have to change

top level parent include actually execute logic about laser system

base_pos as meaning laser startING point 
target_pos as meaning  endpoint

beam root is parent object controlled by'beam script'

You can set the axis offset in the 'transform object' under the beam_root

- this script using  'look at method' offered in unity3d, the method's rotation axis is fixed

   
  So if the axis used is different from the actual axis,

  you will need an intermediate object that needs to be interpolated. 

 

 


 

/////////////////////material(shader)

material inspector

set texture's style in material
set y tile or xtile if texture is tiling texture

x tile is only used to create blocks of textures, like the atlas system


speed - texture animating speed
num - texture sheet  starting number
repet_x - it use in script about how many times repeats in default range
            this script set default range for best visual

 

If the default range is 4, the texture will repeat by repeat_x on scale 4

 

 

this shader is shown how controlling textures 'uv'

Carefully look at how basic tiling and iteration x are connected.

original image

 

repeat_x = 2.25

repeat_x = 1

repeat_x = 2.25 tile x = 2

looks like the image of repeatx = 1 but uses only half of the original image

 

//shader code

 

Shader "Fx_textureanimation"
{
Properties
{
_Texture("Texture", 2D) = "white" {}
_xtile("xtile", Float) = 0
_ytile("ytile", Float) = 0
_num("num", Float) = 0
_repeat_X("repeat_X", Float) = 0
_speed("speed", Float) = 0
[HideInInspector] _texcoord( "", 2D ) = "white" {}
[HideInInspector] __dirty( "", Int ) = 1
}

SubShader
{
Tags{ "RenderType" = "Transparent"  "Queue" = "Transparent+0" "IgnoreProjector" = "True" "IsEmissive" = "true"  }
Cull Off
CGINCLUDE
#include "UnityShaderVariables.cginc"
#include "UnityPBSLighting.cginc"
#include "Lighting.cginc"
#pragma target 3.0
struct Input
{
float2 uv_texcoord;
};

uniform sampler2D _Texture;
uniform float _repeat_X;
uniform float _xtile;
uniform float _ytile;
uniform float _speed;
uniform float _num;

inline half4 LightingUnlit( SurfaceOutput s, half3 lightDir, half atten )
{
return half4 ( 0, 0, 0, s.Alpha );
}

void surf( Input i , inout SurfaceOutput o )
{
float temp_output_4_0 = ( 1.0 / _xtile );
float temp_output_5_0 = ( 1.0 / _ytile );
float2 appendResult15 = (float2(( _repeat_X * temp_output_4_0 ) , temp_output_5_0));
float mulTime25 = _Time.y * _speed;
float temp_output_21_0 = ( mulTime25 + _num );
float2 appendResult8 = (float2(( floor( temp_output_21_0 ) * temp_output_4_0 ) , ( temp_output_5_0 * floor( ( temp_output_21_0 / _xtile ) ) )));
float2 uv_TexCoord3 = i.uv_texcoord * appendResult15 + appendResult8;
float4 tex2DNode1 = tex2D( _Texture, uv_TexCoord3 );
o.Emission = tex2DNode1.rgb;
o.Alpha = tex2DNode1.a;
}

ENDCG
CGPROGRAM
#pragma surface surf Unlit alpha:fade keepalpha fullforwardshadows 

ENDCG
Pass
{
Name "ShadowCaster"
Tags{ "LightMode" = "ShadowCaster" }
ZWrite On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#pragma multi_compile_shadowcaster
#pragma multi_compile UNITY_PASS_SHADOWCASTER
#pragma skip_variants FOG_LINEAR FOG_EXP FOG_EXP2
#include "HLSLSupport.cginc"
#if ( SHADER_API_D3D11 || SHADER_API_GLCORE || SHADER_API_GLES || SHADER_API_GLES3 || SHADER_API_METAL || SHADER_API_VULKAN )
#define CAN_SKIP_VPOS
#endif
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "UnityPBSLighting.cginc"
sampler3D _DitherMaskLOD;
struct v2f
{
V2F_SHADOW_CASTER;
float2 customPack1 : TEXCOORD1;
float3 worldPos : TEXCOORD2;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
v2f vert( appdata_full v )
{
v2f o;
UNITY_SETUP_INSTANCE_ID( v );
UNITY_INITIALIZE_OUTPUT( v2f, o );
UNITY_TRANSFER_INSTANCE_ID( v, o );
Input customInputData;
float3 worldPos = mul( unity_ObjectToWorld, v.vertex ).xyz;
half3 worldNormal = UnityObjectToWorldNormal( v.normal );
o.customPack1.xy = customInputData.uv_texcoord;
o.customPack1.xy = v.texcoord;
o.worldPos = worldPos;
TRANSFER_SHADOW_CASTER_NORMALOFFSET( o )
return o;
}
half4 frag( v2f IN
#if !defined( CAN_SKIP_VPOS )
, UNITY_VPOS_TYPE vpos : VPOS
#endif
) : SV_Target
{
UNITY_SETUP_INSTANCE_ID( IN );
Input surfIN;
UNITY_INITIALIZE_OUTPUT( Input, surfIN );
surfIN.uv_texcoord = IN.customPack1.xy;
float3 worldPos = IN.worldPos;
half3 worldViewDir = normalize( UnityWorldSpaceViewDir( worldPos ) );
SurfaceOutput o;
UNITY_INITIALIZE_OUTPUT( SurfaceOutput, o )
surf( surfIN, o );
#if defined( CAN_SKIP_VPOS )
float2 vpos = IN.pos;
#endif
half alphaRef = tex3D( _DitherMaskLOD, float3( vpos.xy * 0.25, o.Alpha * 0.9375 ) ).a;
clip( alphaRef - 0.01 );
SHADOW_CASTER_FRAGMENT( IN )
}
ENDCG
}
}
Fallback "Diffuse"
CustomEditor "ASEMaterialInspector"
}

 

 

 

 

 

//////////// ///////script

we prepare prefab and materials

Now, we will rotate and scale the beam_root of the prefab so that it looks like a laser

 

a lot of variables shown, but many of variables only usage in debugging, so disappear later

beamdistance - auto calculated in the script(it was used in beam texture base)
   meaning distance between base_pos(starpoint) to target_pos(endpoint)

 

beamtexturebase

  This is what we mentioned in describing the default range of material repeat_x

   if it is 4 and repeat_x is 2

   when the distance between the strt_pos and target_pos is 4( mached default setting )

   texture shown 2 times(repeat 2 time)

   

  this function is useful for animated sequence image work

 

isrepeat - it is check point about texture is repeat or scale

 

life time - it is beam's lifetime 

 

other variables are not used in system almost there is  using in debug or viewing created laser effect 

 

 

///////////////////// code explane

 

 

we check out some point about main features of the laser system

 

 

using System.Collections; 
using System.Collections.Generic; 
using UnityEngine; 

[ExecuteInEditMode] //<<using in editormode

public class Fx_beam_test_C : MonoBehaviour 
{ 
    string baseobject = "Base_pos"; 
    string targetobject = "Target_Pos"; 
    string beamobject = "Beam_Root"; 
    public Vector3 LookatUpvector = new Vector3(0, 0, 0); 

    Vector3 basepos = new Vector3(0, 0, 0); 
    Vector3 targetpos = new Vector3(0, 0, 0); 
    Transform beamobjectTrans; 

    //texture transform parameter //<<variables in this block is for transform
    public float beamdistance = 1; 
    public float beamdTextureBase = 1; 
    public bool isrepeat = false; 
    public float beamlifetime = 0; 
    public float BeamActiveDelay = 0; 
    public float currentlifetime; 

    // material parameter //<<variables for material control
    public float materialrepeatbase = 1; 
    Material beammaterial; 
    Material beammaterialIns; 
    string beambody = "Beam_Body_quad"; 

    // editor only paramter(remove rater) 
    public float viewdelay_editor = 1; 
    public float Tcurrentlifetime = 0; 


    private void OnEnable() 

//<<base variables setting block it is use in editormode

//<<if dont have to cosider 'excuteeditormode', this block move to 'void start()​'

//<<Nothing special, but fill each variable according to the predefined content.
    { 
        basepos = gameObject.transform.Find(baseobject).position; 
        gameObject.transform.Find(beamobject).position = basepos; 

        beamobjectTrans = gameObject.transform.Find(beamobject); 


        // Set material parameter 

        var tempobjectP = beamobjectTrans.transform.Find("transformobject"); 
         
        var tempobject = tempobjectP.transform.Find(beambody); 
        beammaterial = tempobject.GetComponent().sharedMaterial; 
        beammaterialIns = beammaterial; 
        //materialrepeatbase = beammaterial.GetFloat("_repeat_X"); 

        //lifetime reset 

        //start setup 
        beamobjectTrans.gameObject.SetActive(false); 
    } 

    private void OnDisable() //<<reset action
    { 
        Tcurrentlifetime = 0; 
        beammaterial = beammaterialIns; 
    } 


    void Start() 
    { 
         
    } 

    void Update() 
    { 
        Tcurrentlifetime += Time.deltaTime;//시간 더해줌 
//<<'Tcurrentlifetime' is used to force ticking for animation time move to forward

        if (beamlifetime == 0) 
        { 
            if (BeamActiveDelay <= Tcurrentlifetime) 
            { 
                beamobjectTrans.gameObject.SetActive(true); 
                beambodyFN(); 
                currentlifetime = 0; 
            } 
            else 
                return; 
        } 

//<< this block is based for laser action

//<<you will find similar code lines when shown other blocks(check condition > run beambodyFn() > reset)

//<<it was checked block's state,  apply according to prearranged conditions

//<<find correct state using 'if / else' statement 

//<<'beambodyFN()' is includ all actions of laser

 

//<< 'void update ()' only finds the exact time the laser action will be executed

 
        else 
        { 
            currentlifetime += Time.deltaTime;//시간을 계속 더해줌 

            if (currentlifetime <= beamlifetime + BeamActiveDelay) 
            { 
                if (BeamActiveDelay <= Tcurrentlifetime) 
                { 
                    beamobjectTrans.gameObject.SetActive(true); 
                    beambodyFN(); 
                     
                } 
                else 
                    return; 
            } 


            else if (currentlifetime <= beamlifetime + BeamActiveDelay + viewdelay_editor) 
            { 
                beamobjectTrans.gameObject.SetActive(false); 
            } 
            else 
            { 
                if (BeamActiveDelay <= Tcurrentlifetime) 
                { 
                    //beamobjectTrans.gameObject.SetActive(true); 
                    beambodyFN(); 
                    currentlifetime = 0; 
                    Tcurrentlifetime = 0; 
                } 
                else 
                    return; 
              
            } 

        } 
    } 
//<< 'void update()' block's main feature compares timeline for delay, repeat, and editor work
//<< it was don't necessary for built game, just working on the assets creating situations


             

//<<now, lower blocks is including main feature for laser work

//<<the work looks at the target, scaling laser body, matching texture tiling 


    void beambodyFN() 
    { 
        targetpos = gameObject.transform.Find(targetobject).position; 

 //​<<get target's transform, the target was positioned something locations by game logic


        beamdistance = Vector3.Distance(basepos, targetpos); 
//<< Calculate distance between target and player, that is using in tiling texture
        beamobjectTrans.localScale = new Vector3(1, 1, beamdistance); 

//<<matching beambody's size to beam distance, the mesh used for the beambody is 1 in size

if your mesh size is not 1, you multiply or divide 'beam distance'

        beamobjectTrans.LookAt(targetpos); 
//<rotate axis of beambody by 'lookat' function

unity offered 'lookat' function is locked axis so we made transformobject in laser prefab


        if (beamdistance < beamdTextureBase) 
        { 
            beammaterialIns.SetFloat("_repeat_X", (beamdistance / beamdTextureBase) * materialrepeatbase); 
        } 

//<Forces the texture to repeat when lower than the minimum size(=beamdtexturebase)
        else 
        { 
            if (isrepeat == true) 
            { 
                beammaterialIns.SetFloat("_repeat_X", (beamdistance / beamdTextureBase) * materialrepeatbase); 
            } 
            else 

//< this block is controling texture repeat or scale

simply//< this block is controlling texture repeat or scale
it's not just multiplied distance/texture base, set minimum and convert scale texture to repeat

when lower than minimum size 

                return; 
        } 
    } 
}

 

 

 

it is simple scripts looks like laser system

but useful for casual style game and that had many points about extend or upgrade

 

 

 

 

 

/////////////////////full code

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[ExecuteInEditMode]

public class Fx_beam_test_C : MonoBehaviour
{
    string baseobject = "Base_pos";
    string targetobject = "Target_Pos";
    string beamobject = "Beam_Root";
    public Vector3 LookatUpvector = new Vector3(0, 0, 0);
    

    Vector3 basepos = new Vector3(0, 0, 0);
    Vector3 targetpos = new Vector3(0, 0, 0);
    Transform beamobjectTrans;

    //texture transform parameter
    public float beamdistance = 1;
    public float beamdTextureBase = 1;
    public bool isrepeat = false;
    public float beamlifetime = 0;
    public float BeamActiveDelay = 0;
    
    public float currentlifetime;

    // material parameter
    public float materialrepeatbase = 1;
    Material beammaterial;
    Material beammaterialIns;
    //public Transform tempobject;
    //public Transform tempobjectP;
    string beambody = "Beam_Body_quad";

    // editor only paramter(remove rater)
    public float viewdelay_editor = 1;
    public float Tcurrentlifetime = 0;


    private void OnEnable()
    {
        basepos = gameObject.transform.Find(baseobject).position;
        //targetpos = gameObject.transform.Find(targetobject).localPosition;
        gameObject.transform.Find(beamobject).position = basepos;

        beamobjectTrans = gameObject.transform.Find(beamobject);

        var tempobjectP = beamobjectTrans.transform.Find("transformobject");
        
        var tempobject = tempobjectP.transform.Find(beambody);
        beammaterial = tempobject.GetComponent().sharedMaterial;
        beammaterialIns = beammaterial;

         
        //start setup
        beamobjectTrans.gameObject.SetActive(false);
        

    }

    private void OnDisable()
    {
        Tcurrentlifetime = 0;
        beammaterial = beammaterialIns;
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {


        Tcurrentlifetime += Time.deltaTime;//시간 더해줌

        if (beamlifetime == 0)
        {
            if (BeamActiveDelay <= Tcurrentlifetime)
            {
                beamobjectTrans.gameObject.SetActive(true);
                beambodyFN();
                currentlifetime = 0;
            }
            else
                return;
            
        }

        else
        {
            currentlifetime += Time.deltaTime;//시간을 계속 더해줌

            if (currentlifetime <= beamlifetime + BeamActiveDelay)
            {
                if (BeamActiveDelay <= Tcurrentlifetime)
                {
                    beamobjectTrans.gameObject.SetActive(true);
                    beambodyFN();
                    
                }
                else
                    return;
            }
            else if (currentlifetime <= beamlifetime + BeamActiveDelay + viewdelay_editor)
            {
                beamobjectTrans.gameObject.SetActive(false);
            }
            else
            {
                if (BeamActiveDelay <= Tcurrentlifetime)
                {
                    //beamobjectTrans.gameObject.SetActive(true);
                    beambodyFN();
                    currentlifetime = 0;
                    Tcurrentlifetime = 0;
                }
                else
                    return;
             }
        }
    }


            


    void beambodyFN()
    {
        targetpos = gameObject.transform.Find(targetobject).position;
        beamdistance = Vector3.Distance(basepos, targetpos);

        beamobjectTrans.localScale = new Vector3(1, 1, beamdistance);
        beamobjectTrans.LookAt(targetpos);
        //--------material / texture setting
        if (beamdistance < beamdTextureBase)
        {
            beammaterialIns.SetFloat("_repeat_X", (beamdistance / beamdTextureBase) * materialrepeatbase);
        }
        else
        {
            if (isrepeat == true)
            {
                beammaterialIns.SetFloat("_repeat_X", (beamdistance / beamdTextureBase) * materialrepeatbase);
            }
            else
                return;
        }
    }
}

 

 

 

our project use sprite image

like this

 

Sprite image shadows usually use a circle image, but the team wanted the shadows to animate

But since my project's drawcall was already full, using more images is not good for our game performance

 

my idea is created fake shadow 

 

my project is summoning many 'characer',  do each character's quality is not important
point is how good feels on screen(acting and animating)

 

howevere, we faced some probolome after decide take that direction 'animating shadow following main character'

we increase no more drawcall

 

look this image

shadow on this image is animating

 

it's simple fake 

same material same mesh does not increase drawcall

if so, the shadow sprites had to get the materials and sprites from the parent sprites

and just make the colors look like black

 

The above image is copied parents material and sprite, and change color

 

on image's  up inspector is main sprite, down is shadow

that script is creating instance shadow plane with parents materials and sprite

after that, change shadow panel's vertex color on sprite renderer

 

before that,  prepare the parents material able to applied vertex color input 

 

now show script code

 

this script is run on editormode 

so, not enough optimizing

 

 

////////////////////////////////////// createshadow.cs

 

using UnityEngine;


[ExecuteInEditMode] //<< this scripts run on editor mode


public class Create_shadow : MonoBehaviour
{
    public Material setMemberInput;
    public Vector3 shadowPos = new Vector3(0f, -0.53f, 0.17f);
    public Vector3 shadowangle = new Vector3(147f, 0f, 0f);
    public Vector3 shadowScale = new Vector3(1f, 0.5f, 1f);
    private Transform findG;
//<<set variables

//<<The reason all variables are declared 'public' is because the current step is development

// << The artist sets the parameter by looking at the variable and game scene

      looks like how?



    void Update()
    {
        var find = gameObject.transform.Find("shadow_ins");
//<<this line is only use in editor mode, don't need to check it in real game scripts

/ << create shadow plane if result doesn't have shadow panel, otherwise update sprite image
        if (find == null) /<<this block is said about creating shadow sprite
        {
            var gameObject = new GameObject("shadow_ins"); /<<prepare variable for create sprite plane

            gameObject.transform.parent = this.gameObject.transform;
            var addComponent = gameObject.AddComponent(typeof(SpriteRenderer));

/<<'add sprite renderer component to gameobject'


            var shadowRender = addComponent.GetComponent();

/<<connet to new sprite renderer component(named 'addcomponent')

 

            shadowRender.transform.localPosition = shadowPos;
            shadowRender.transform.localEulerAngles = shadowangle;
            shadowRender.transform.localScale = shadowScale;

/<<set shadow plane's transform from prepared variables


            setMemberInput = this.GetComponent().sharedMaterial;

/<< material instantiation with the 'sharedmaterial' command
Be careful not to use the .material command
.material command will be increasing drawcall

            shadowRender.material = setMemberInput;
            shadowRender.color = new Color(0f, 0f, 0f, 0.5f);

/<< aplly material and color to sprite renderer

/<<  focus change color is not material color, it is sprite.color, 

 

if you change material color is increasing drawcall
the reason is any changing material parameter  action is creating other materials(instancing)

 

actually, unity served default sprite material is  able receiving input vertexcolor
and sprite renderer's color change meaning change vertexcolor

 

Extending this fact you get 4 parameters (rgba) that you can use without increasing drawcall.
Also, if you have a parameter that can be automatically received 
when creating a material

(normal or other parameters that can be changed without access to the material),

 

 you can express many things without increasing draw calls

 



        }
        else
        {
            var getComponent = gameObject.GetComponent().GetComponent();
            var getComponent2 = find.GetComponent();

            getComponent2.sprite = getComponent.sprite;

 

/<< update sprite from main object if already shadow creted

 

        }

    }
}

 

//////////////////////////////////////////////////////////

 

The best quality is crafting the real shadow, but the solution leads very heavy calculations

This solution provides a medium quality screen that looks like something is animated(or moving)

 

if your game is sponed many character and battle on same time(in chaotic screen)
this solution will be upgrading whole view quality

it is very simple and useful code example

Extending the code saves time for team members.

 

used for simple and repeated work

this code change all name of animationclip to lowercase

if you need others(game object, prefab ..) just change the filter

this code locate the file name of the animationclip under the target folder and change it to lowercase

many parts overlapped at 'create sprite animation sheet'

 

 

////////////  - code detail explanation //// full code  is under this block

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Linq;
using UnityEditor.Animations;

/<< use unity system, io, linq, unity animationsystem 

/<< system.io use at string control
/<< system.linq use at string Enum

 


public class StringToLower : EditorWindow
{
    static StringToLower myWindow;
    Object targetObj;

    string[] fileTypeNames = { "폴더", "더미생성" };
    int fileType = 0;
    string[] guids;
/<< many time explane this block, set base vriables


    [MenuItem("Window/stringtolower")]
    static void Init()
    {
        // Get existing open window or if none, make a new one:
        myWindow = (StringToLower)GetWindow(typeof(StringToLower));

/<< this editor name is stringtolower 
        myWindow.Show();
    }

    private void OnDestroy()
    {
        CloseSpriteViewerWindow();
    }

    void CloseSpriteViewerWindow()
    {
        Reset();
    }



    void Reset()
    {
        targetObj = null;
    }
/<< all custom editor used parameter or method{reset, close, destory..) 

    void OnGUI()
    {
        int type = EditorGUILayout.Popup(fileType, fileTypeNames, GUILayout.Height(20));
        if (fileType != type)
        {
            CloseSpriteViewerWindow();
            fileType = type;
            Reset();
        }

        if (fileType == 0)
        {
            var obj = EditorGUILayout.ObjectField("폴더 :", targetObj, typeof(DefaultAsset), true);
            if (targetObj != obj)
            {
                targetObj = obj;
            }
            else
            { 
                if (targetObj != null)
                {
                    if (GUILayout.Button("문자바꾸기", GUILayout.Height(40)))

/<< click button action

                    {
                        var tempPath = AssetDatabase.GetAssetPath(targetObj);

/<<this line already explained, temp path set target folder's path
                        string[] guids = AssetDatabase.FindAssets("t:AnimationClip", new string[] { tempPath });
/<<find animationclip and put to guids array
                        for (int i = 0; i < guids.Length; ++i)
                        {
                            string path = AssetDatabase.GUIDToAssetPath(guids[i]);
/<<take materials path                            
                            string[] tempsprit = path.Split('/');

/<<Splits a path into '/' to separate folders and resources

/<<for example c:/work/a.clip to [c:, work, a.clip]
                            var lastindex = tempsprit.Length;
                            string localpath = path.Replace(tempsprit[lastindex - 1], "");
                            string newname = tempsprit[lastindex-1].ToLowerInvariant();
/<<Converts all separated resource names to lower case.
                           // System.IO.File.Move(path, localpath + newname);
                           // System.IO.File.Move(path+".meta", localpath + newname+".meta");
/<<system.io not used in this code but it is useful code line when assetdatabase.rename can't use
/ << this line is based on c # reference, c # do not have 'rename file action' so replace  'move with new name'           
                            AssetDatabase.RenameAsset(path, newname);

/ <<unity rename acrion it nees origine full path(c:/aaa/bbb/c.png) and target resource name(a.png)
                            AssetDatabase.SaveAssets();
                            AssetDatabase.Refresh();
                            
                        }
                    }
                }
            
            }
        }
    }
}

 

 

 

//////////////////////////////full code

 

 

 

using System.Collections; 
using System.Collections.Generic; 
using UnityEngine; 
using UnityEditor; 
using System.IO; 
using System.Linq; 
using UnityEditor.Animations; 

public class StringToLower : EditorWindow 
{ 
    static StringToLower myWindow; 
    Object targetObj; 


    int frameSamples = 10; 
    int spriteInterval = 1; 

    string[] fileTypeNames = { "폴더", "더미생성" }; 
    int fileType = 0; 

  
    string[] guids; 

    [MenuItem("Window/stringtolower")] 
    static void Init() 
    { 
        // Get existing open window or if none, make a new one: 
        myWindow = (StringToLower)GetWindow(typeof(StringToLower)); 
        myWindow.Show(); 
    } 

    private void OnDestroy() 
    { 
        CloseSpriteViewerWindow(); 
    } 

    void CloseSpriteViewerWindow() 
    { 
        Reset(); 
    } 

    void Reset() 
    { 
         
        targetObj = null; 
    } 


    void OnGUI() 
    { 
        int type = EditorGUILayout.Popup(fileType, fileTypeNames, GUILayout.Height(20)); 
        if (fileType != type) 
        { 
            CloseSpriteViewerWindow(); 
            fileType = type; 
            Reset(); 
        } 

        if (fileType == 0) 
        { 
            var obj = EditorGUILayout.ObjectField("폴더 :", targetObj, typeof(DefaultAsset), true); 
            if (targetObj != obj) 
            { 
                targetObj = obj; 
            } 
            else 
            {  
                if (targetObj != null) 
                { 
                    if (GUILayout.Button("문자바꾸기", GUILayout.Height(40))) 
                    { 

                        var tempPath = AssetDatabase.GetAssetPath(targetObj); 
                        string[] guids = AssetDatabase.FindAssets("t:AnimationClip", new string[] { tempPath }); 

                        for (int i = 0; i < guids.Length; ++i) 
                        { 
                            string path = AssetDatabase.GUIDToAssetPath(guids[i]); 
                             
                             
                            string[] tempsprit = path.Split('/'); 
                            var lastindex = tempsprit.Length; 
                            Debug.Log(lastindex); 
                            string localpath = path.Replace(tempsprit[lastindex - 1], ""); 
                            string newname = tempsprit[lastindex-1].ToLowerInvariant(); 

                           // System.IO.File.Move(path, localpath + newname); 
                           // System.IO.File.Move(path+".meta", localpath + newname+".meta"); 

                             
                            AssetDatabase.RenameAsset(path, newname); 
                                     
                            AssetDatabase.SaveAssets(); 
                            AssetDatabase.Refresh(); 
                             
                        } 
                    } 
                } 
             
            } 
        } 
    } 
}

our team project is base on 2d sprite image

 

craft art resource solution is very simple

 

create 3d model (3d max)

create an image on un4 engine

import image ue4 to unity

make sprite animation

end

 

but our team is not used code sprite animation, use animation clip
because it was more high quality

using animation clip is more efficient pull up quality level

 

but those work was simple and repetitive 

sprite image into the animation clip and control spacing

every character had  4direction = 4 animation clips and 3 level variation = 12 animation clips

 

animation artist has suffered to do that work

 

request

 

completed animation

prefixed condition

- have completed art resource(sprite)

- have fixed animation frame

needs

-  assign animatorcontroller after create animation clip & animatorcontroller

- control sprite interval(look like under)

 

not complete yet

prefixed condition

- had only resource name(from game article designer)

- create image later

needs

- want to do the same action by resource name like when I had a real image(select 1)

- want to be able to control the dummy name, spacing, sprite start number, and number of sprites 

  when creating a sprite.

 

 

 

 

 

 

 

 

 

/////  - code detail explanation //// full code  is under this block

 

 

sing System.Collections; 
using System.Collections.Generic; 
using UnityEngine; 
using UnityEditor; 
using System.IO; 
using System.Linq; 
using UnityEditor.Animations; 

public class CreateSpriteAnimationClip : EditorWindow 

{ 
    static CreateSpriteAnimationClip myWindow; 
/<< declare "use editor window


    Object targetObj; 
    int frameSamples = 10; 
    int spriteInterval = 1; 

    string[] fileTypeNames = { "폴더", "더미생성" }; 
    int fileType = 0; 
/<< variables use in type select 


    // 더미생성용 변수 
    string spritenamefield = "dummyName"; 
    string spritefolderfield = "Assets/artResources/"; 
    string oridummysprite = "Assets/artResources/Characters/spritedefault.png"; 
    int StarSpriteNum = 0; 
    public AnimationClip MyAnimator = null; 
    public Texture2D dummytexture; 
    Sprite Dummysprite; 
    bool RemoveDummySprite = true; 
    int DummynameInterval = 1; 
/<< this block use in Dummy sprite

/<< copy from oridummysprite to dummy sprite and rename it with dummy name


    string outputPath; 
    Sprite[] sprites; 

    [MenuItem("Window/CreateSpriteAnimationClip")] 
    static void Init() 
    { 
        // Get existing open window or if none, make a new one: 
        myWindow = (CreateSpriteAnimationClip)GetWindow(typeof(CreateSpriteAnimationClip)); 
        myWindow.Show(); 
    } 
/<< base setting menuitem name,, base action - open custom editor when you click window menu 

    private void OnDestroy() 
    { 
        CloseSpriteViewerWindow(); 
    } 

    void CloseSpriteViewerWindow() 
    { 
        Reset(); 
    } 

    void Reset() 
    { 
        outputPath = ""; 
        sprites = null; 
        targetObj = null; 
    } 

/<<it's base setting about close, end scripts path, sprite array, target folder reset


    void OnGUI() 
    { 
        int type = EditorGUILayout.Popup(fileType, fileTypeNames, GUILayout.Height(20)); 

/<<Create popup menu for category selection
        if (fileType != type) 
        { 
            CloseSpriteViewerWindow(); 
            fileType = type; 
            Reset(); 
        } 

/<<if not match type do reset funtion

        if (fileType == 0) 
        { 
            var obj = EditorGUILayout.ObjectField("폴더 :", targetObj, typeof(DefaultAsset), true); 


            if (targetObj != obj) 
            { 
                targetObj = obj; 
                if (targetObj != null) 
                { 
                    var tempPath = AssetDatabase.GetAssetPath(targetObj); 

/<<temppath is targetobject's asset path this case target is folder path because targetobject is pointing folder

ex)d:/work/.../asset/a/

/<<reason for getting folder path was each computer's realpath is different
/<<in this scripts some line needs project path, some line absoulte path

 

/<<use the real path getting from the target object to avoid confusion about the "path" 

 

/<<this block is unity offical guid about 'assetdatabase.find assets'


                    string[] guids = AssetDatabase.FindAssets("t:Texture", new string[] { tempPath }); 

 

/<< find sprites(t:texture = type filter) in the target folder and put in to guid[] each file

/<< get the number of guid[](comand guids[].length)

/<< It will be used to how many times the block should be run


                    List tempSprites = new List(); 
                    for (int i = 0; i < guids.Length; ++i) 
                    { 
                        string path = AssetDatabase.GUIDToAssetPath(guids[i]); 

/<< we know array value is pointer(memory address), not a real value so convert pointer to real data


                        tempSprites.AddRange(AssetDatabase.LoadAllAssetsAtPath(path).OfType().ToArray()); 
                    } 

/<<create path string from guids[i](pointer) every each time

                    if (tempSprites.Count > 0) 
                    { 
                        outputPath = tempPath; 
                        sprites = tempSprites.ToArray(); 

/<<that setting outputpath and sprite array when folder have sprite more than 1 
                    } 
                    else 
                    { 
                        EditorUtility.DisplayDialog("경고", "해당 폴더에 스프라이트 파일 정보가 존재 하지 않습니다.\n폴더 확인 후 다시 이용해주세요.", "확인"); 
                        Reset(); 
                    } 
                } 
                else 
                    Reset(); 
            } 
        } 

/<<under block is using when type is not eaqual 0

/<<create dummy animation clip with dummy sprite name
        else 
        { 
            spritenamefield = EditorGUILayout.TextField("스프라이트이름 : ", spritenamefield); 
            spritefolderfield = EditorGUILayout.TextField("스프라이트지정폴더 : ", spritefolderfield); 
            StarSpriteNum = EditorGUILayout.IntField("시작스프라이트 번호 : ", StarSpriteNum); 
            DummynameInterval = EditorGUILayout.IntField("더미이름간격", DummynameInterval); 
            oridummysprite = EditorGUILayout.TextField("원본스프라이트경로 : ", oridummysprite); 
            RemoveDummySprite = EditorGUILayout.Toggle("더미스프라이트 삭제 : ", RemoveDummySprite); 
/<<variable block appear on custom editor, dummy data control based on variables


            if (GUILayout.Button("에셋 생성", GUILayout.Height(40))) 
            { 
                if (spritenamefield != null) 
                { 
                   var Sourceasset = oridummysprite; 

                    //실질적으로 작동하는 부분 
                    //순서는 스프라이트 에셋 생성 > 애니매이션 키 생성 > 스프라이트 에셋 삭제 

                    for (int i = 0; i < frameSamples; ++i) 
                    { 
                        int filenum = ((i * DummynameInterval) + StarSpriteNum); 
                        string ilocal = string.Format("{0:00}", filenum);                                      

                        var tempsprite = spritefolderfield + "/" + spritenamefield +"_"+ilocal + ".png"; 

                        AssetDatabase.CopyAsset(Sourceasset, tempsprite); 

/<< create temp file name from current count(i) + interval + first number(startspritenum)

/<<copy origin image(sourceasset) to temporary image by current name(tempsprite)

     using 'assetdatbase.copyasset'

/<<so we had temp image file named 'dummy name + i*dummy*strtnum+.png'

/<<we got the temporary image of number 'i' using the for the statement.

 

                   } 

/<<this block is very useful code block, also quoted many times  in official unity_reference


                    var tempPath = spritefolderfield; /<< create path, it used in create final path
                    string[] guids = AssetDatabase.FindAssets("t:Texture", new string[] { tempPath }); 

/<<find assets filtered by texture(T:texture)in folder(temp path) and put it to guides[]  
                    List tempSprites = new List(); 

/<<create new array(list) it using at spritearray

                    for (int i = 0; i < guids.Length; ++i) 
                    { 
                        string path = AssetDatabase.GUIDToAssetPath(guids[i]); 

/<<create path with guid[i] 

/<< we already know array is not real value it just memory address
       so we have to  convert address to the value

/<< depend on 'for statement' path is real value for each gui[i]
                        tempSprites.AddRange(AssetDatabase.LoadAllAssetsAtPath(path).OfType().ToArray()); 
                    } 
/<<tempsprite' each line filled by path[i]

now we take filled array with each sprite's path = tempsprite[] = all spriteimage's path

                    outputPath = tempPath; 
                    sprites = tempSprites.ToArray(); /<<list to array



                    // 배열 리스트 생성 
                      CreateAnimationClip(); 
/<<create clip method


                    //사용하고난 더미 파일 삭제 
                    if (RemoveDummySprite == true) /<<check branch, remove used sprites
                    { 
                        for (int i = 0; i < frameSamples; ++i) 
                        { 
                            int filenum = ((i * DummynameInterval) + StarSpriteNum); 
                            //string ilocal = fileNum.ToString(); 
                            string ilocal = string.Format("{0:00}", filenum); 
/<<same action create situation
                            var tempsprite = spritefolderfield + "/" + spritenamefield + "_" + ilocal + ".png"; 
                            AssetDatabase.DeleteAsset(tempsprite); 

/<<delete all created sprites
                        } 
                    } 
                    else 
                    { 
                        Reset(); /<<reset varialbe value
                    } 
                     
} 
                else 
                { 
                    EditorUtility.DisplayDialog("경고", "생성될 파일 이름을 지정해주세요.", "확인"); 
                    Reset(); 
                } 
            } 
          } 

/<<
        frameSamples = Mathf.Max(0, EditorGUILayout.IntField("프레임 : ", frameSamples)); 
        spriteInterval = Mathf.Max(1, EditorGUILayout.IntField("간격 : ", spriteInterval)); 

        GUILayout.Label("총 스프라이트 갯수 : " + (sprites != null ? sprites.Length : 0)); 
        GUI.enabled = targetObj != null; 
/<< this block is show up variables(used on gui)

/<<
        if (GUILayout.Button("애니메이션 생성", GUILayout.ExpandHeight(true))) 
        { 
             CreateAnimationClip(); 
             } 
    } 

    void CreateAnimationClip() /<<create clip method
    { 
        AnimationClip animClip = new AnimationClip(); 
        animClip.frameRate = 30; 
        animClip.wrapMode = WrapMode.Loop; 
/<<base variable set

/<<we using this variables at create animartion clip

 

/<< Now, we were asked to create an animation clip and assign it to the controller.

/<<proccess is 1. create animationcontroller/2.createclips/3.create animation key/4.assign to controller

       AnimatorController controller = new AnimatorController(); /<<create new controller
        controller.AddLayer("newlayer"); 

/<<create new unity animation controller is don't have any layer first create new layer

/<<we had useable animationcontroller with empty layer it using later 
       
        var spriteBinding = EditorCurveBinding.PPtrCurve("", typeof(SpriteRenderer), "m_Sprite"); 

/<<set variable for creating animation clips track 

/<<'spritebinding' define tracks properties for what type use animation

 


/ << This code block is familiar to us, creates array ~, sets a variable, and repeats it by the number of [i].

/ << there are only a few codes that we meet the first time


        ObjectReferenceKeyframe[] spriteKeyFrames = new ObjectReferenceKeyframe[sprites.Length]; /array
        for (int i = 0; i < sprites.Length; i++) /repeat i times
        { 
            spriteKeyFrames[i] = new ObjectReferenceKeyframe(); 
            float unitTime = 1f / 30; /make frame time(match time to frame time)


            spriteKeyFrames[i].time = spriteInterval * i * unitTime; 

/we go to exact the time and create key(not every frame)
            spriteKeyFrames[i].value = sprites[i]; 

/set sprite on key

/if you make this action on 'runtime' you must using time.deltatime

but i'm not use time, just divide 'time/frame' because receiv request is only run in editor 
        } 

 

        string filePathLocal; 
        string filePathConLocal; 
        AnimationUtility.SetObjectReferenceCurve(animClip, spriteBinding, spriteKeyFrames); 

//create animation track (see official reference), here using parameters what we created on first time


         
            if (fileType == 0) 
            { 
                filePathLocal = Path.Combine(outputPath, (targetObj.name) + ".anim"); 
                filePathConLocal = Path.Combine(outputPath, (targetObj.name) + ".controller"); 
            } 
            else 
            { 
                filePathLocal = Path.Combine(outputPath, spritenamefield + ".anim"); 
                filePathConLocal = Path.Combine(outputPath, spritenamefield + ".controller"); 
            } 
// filetype = 1 = make real data, 2 = dummy data


        AssetDatabase.CreateAsset(controller, filePathConLocal); 
        AssetDatabase.CreateAsset(animClip, filePathLocal); 
        AssetDatabase.SaveAssets(); 
        AssetDatabase.Refresh(); 
//finally save assets
        controller.AddMotion(animClip, 0); 

// clip assign to layer 0

    }

 

 

 

///////////////////////// full code ////////////////////////

 

 

sing System.Collections; 
using System.Collections.Generic; 
using UnityEngine; 
using UnityEditor; 
using System.IO; 
using System.Linq; 
using UnityEditor.Animations; 

public class CreateSpriteAnimationClip : EditorWindow 
{ 
    static CreateSpriteAnimationClip myWindow; 
    Object targetObj; 


    int frameSamples = 10; 
    int spriteInterval = 1; 

    string[] fileTypeNames = { "폴더", "더미생성" }; 
    int fileType = 0; 

    // 더미생성용 변수 
    string spritenamefield = "dummyName"; 
    string spritefolderfield = "Assets/artResources/"; 
    string oridummysprite = "Assets/artResources/Characters/spritedefault.png"; 
    int StarSpriteNum = 0; 
    public AnimationClip MyAnimator = null; 
    public Texture2D dummytexture; 
    Sprite Dummysprite; 
    bool RemoveDummySprite = true; 
    int DummynameInterval = 1; 



    string outputPath; 
    Sprite[] sprites; 

    [MenuItem("Window/CreateSpriteAnimationClip")] 
    static void Init() 
    { 
        // Get existing open window or if none, make a new one: 
        myWindow = (CreateSpriteAnimationClip)GetWindow(typeof(CreateSpriteAnimationClip)); 
        myWindow.Show(); 
    } 

    private void OnDestroy() 
    { 
        CloseSpriteViewerWindow(); 
    } 

    void CloseSpriteViewerWindow() 
    { 
        Reset(); 
    } 

    void Reset() 
    { 
        outputPath = ""; 
        sprites = null; 
        targetObj = null; 
    } 


    void OnGUI() 
    { 
        int type = EditorGUILayout.Popup(fileType, fileTypeNames, GUILayout.Height(20)); 
        if (fileType != type) 
        { 
            CloseSpriteViewerWindow(); 
            fileType = type; 
            Reset(); 
        } 

        if (fileType == 0) 
        { 
            var obj = EditorGUILayout.ObjectField("폴더 :", targetObj, typeof(DefaultAsset), true); 
            if (targetObj != obj) 
            { 
                targetObj = obj; 
                if (targetObj != null) 
                { 
                    var tempPath = AssetDatabase.GetAssetPath(targetObj); 
                    string[] guids = AssetDatabase.FindAssets("t:Texture", new string[] { tempPath }); 
                    List tempSprites = new List(); 
                    for (int i = 0; i < guids.Length; ++i) 
                    { 
                        string path = AssetDatabase.GUIDToAssetPath(guids[i]); 
                        tempSprites.AddRange(AssetDatabase.LoadAllAssetsAtPath(path).OfType().ToArray()); 
                    } 

                    if (tempSprites.Count > 0) 
                    { 
                        outputPath = tempPath; 
                        sprites = tempSprites.ToArray(); 
                    } 
                    else 
                    { 
                        EditorUtility.DisplayDialog("경고", "해당 폴더에 스프라이트 파일 정보가 존재 하지 않습니다.\n폴더 확인 후 다시 이용해주세요.", "확인"); 
                        Reset(); 
                    } 
                } 
                else 
                    Reset(); 
            } 
        } 
        else 
        { 
            spritenamefield = EditorGUILayout.TextField("스프라이트이름 : ", spritenamefield); 
            spritefolderfield = EditorGUILayout.TextField("스프라이트지정폴더 : ", spritefolderfield); 
            StarSpriteNum = EditorGUILayout.IntField("시작스프라이트 번호 : ", StarSpriteNum); 
            DummynameInterval = EditorGUILayout.IntField("더미이름간격", DummynameInterval); 
            oridummysprite = EditorGUILayout.TextField("원본스프라이트경로 : ", oridummysprite); 
            RemoveDummySprite = EditorGUILayout.Toggle("더미스프라이트 삭제 : ", RemoveDummySprite); 

            if (GUILayout.Button("에셋 생성", GUILayout.Height(40))) 
            { 
                if (spritenamefield != null) 
                { 
                    // Create a simple material asset 

                
                    var Sourceasset = oridummysprite; 

                    //실질적으로 작동하는 부분 
                    //순서는 스프라이트 에셋 생성 > 애니매이션 키 생성 > 스프라이트 에셋 삭제 

                    for (int i = 0; i < frameSamples; ++i) 
                    { 
                        int filenum = ((i * DummynameInterval) + StarSpriteNum); 
                        //string ilocal = fileNum.ToString(); 
                        string ilocal = string.Format("{0:00}", filenum); 
                         
                         
                        var tempsprite = spritefolderfield + "/" + spritenamefield +"_"+ilocal + ".png"; 
                        AssetDatabase.CopyAsset(Sourceasset, tempsprite); 

                    } 


                    var tempPath = spritefolderfield; 
                    string[] guids = AssetDatabase.FindAssets("t:Texture", new string[] { tempPath }); 
                    List tempSprites = new List(); 
                    for (int i = 0; i < guids.Length; ++i) 
                    { 
                        string path = AssetDatabase.GUIDToAssetPath(guids[i]); 
                        tempSprites.AddRange(AssetDatabase.LoadAllAssetsAtPath(path).OfType().ToArray()); 
                    } 

                    outputPath = tempPath; 
                    sprites = tempSprites.ToArray(); 

                    // 배열 리스트 생성 
                      CreateAnimationClip(); 

                    //사용하고난 더미 파일 삭제 
                    if (RemoveDummySprite == true) 
                    { 
                        for (int i = 0; i < frameSamples; ++i) 
                        { 
                            int filenum = ((i * DummynameInterval) + StarSpriteNum); 
                            //string ilocal = fileNum.ToString(); 
                            string ilocal = string.Format("{0:00}", filenum); 


                            var tempsprite = spritefolderfield + "/" + spritenamefield + "_" + ilocal + ".png"; 
                            AssetDatabase.DeleteAsset(tempsprite); 
                        } 
                    } 
                    else 
                    { 
                        Reset(); 
                    } 
                     
} 
                else 
                { 
                    EditorUtility.DisplayDialog("경고", "생성될 파일 이름을 지정해주세요.", "확인"); 
                    Reset(); 
                } 
            } 
          } 


        frameSamples = Mathf.Max(0, EditorGUILayout.IntField("프레임 : ", frameSamples)); 

        spriteInterval = Mathf.Max(1, EditorGUILayout.IntField("간격 : ", spriteInterval)); 

        GUILayout.Label("총 스프라이트 갯수 : " + (sprites != null ? sprites.Length : 0)); 

        GUI.enabled = targetObj != null; 


        if (GUILayout.Button("애니메이션 생성", GUILayout.ExpandHeight(true))) 
        { 
             CreateAnimationClip(); 
             } 
    } 

    void CreateAnimationClip() 
    { 
        AnimationClip animClip = new AnimationClip(); 
        animClip.frameRate = 30; 
        animClip.wrapMode = WrapMode.Loop; 


        AnimatorController controller = new AnimatorController(); 

        controller.AddLayer("newlayer"); 
       
        var spriteBinding = EditorCurveBinding.PPtrCurve("", typeof(SpriteRenderer), "m_Sprite"); 

        ObjectReferenceKeyframe[] spriteKeyFrames = new ObjectReferenceKeyframe[sprites.Length]; 
        for (int i = 0; i < sprites.Length; i++) 
        { 
            spriteKeyFrames[i] = new ObjectReferenceKeyframe(); 
            float unitTime = 1f / 30; 
            spriteKeyFrames[i].time = spriteInterval * i * unitTime; 
            spriteKeyFrames[i].value = sprites[i]; 
        } 
        string filePathLocal; 
        string filePathConLocal; 
        AnimationUtility.SetObjectReferenceCurve(animClip, spriteBinding, spriteKeyFrames); 
         
            if (fileType == 0) 
            { 
                filePathLocal = Path.Combine(outputPath, (targetObj.name) + ".anim"); 
                filePathConLocal = Path.Combine(outputPath, (targetObj.name) + ".controller"); 
            } 
            else 
            { 
                filePathLocal = Path.Combine(outputPath, spritenamefield + ".anim"); 
                filePathConLocal = Path.Combine(outputPath, spritenamefield + ".controller"); 
            } 

        AssetDatabase.CreateAsset(controller, filePathConLocal); 
         
        AssetDatabase.CreateAsset(animClip, filePathLocal); 

        AssetDatabase.SaveAssets(); 
        AssetDatabase.Refresh(); 
 
        controller.AddMotion(animClip, 0); 

    }

+ Recent posts