Unity warlock incinerate
2017-12-10 / 6 min read
Simulate warlock's spell - Incinerate in world of warcraft:
Construct the mesh and update it each frame
Initialize vertices of mesh
var vertices = new Vector3[(SEGMENTS_COUNT + 1) * 2];
for (var i = 0; i < vertices.Length; i++) {
vertices [i] = transform.position;
}
1. Initialize uv coordinates of mesh
// same length with vertices array
var uvs = new Vector2[vertices.Length];
for (var i = 0; i < uvs.Length; i++) {
float x = 0, y = i / (float)SEGMENTS_COUNT;
if (i % 2 == 0) {
x = 0; // left vertex
} else {
x = 1; // right vertex
}
uvs [i] = new Vector2 (x, y);
}
2. Initialize indices of mesh
// each segment has two trangles and each trangle has three vertices
var indices = new int[SEGMENTS_COUNT * 2 * 3];
for (int i = 0; i < SEGMENTS_COUNT; i++) {
indices [6 * i + 0] = 0 + i * 2;
indices [6 * i + 1] = 1 + i * 2;
indices [6 * i + 2] = 2 + i * 2;
indices [6 * i + 3 + 0] = 2 + i * 2;
indices [6 * i + 3 + 1] = 1 + i * 2;
indices [6 * i + 3 + 2] = 3 + i * 2;
}
3. Fill into mesh
mMesh = meshFilter.mesh = new Mesh ();
mMesh.vertices = vertices;
mMesh.uv = uvs;
mMesh.SetIndices (indices, MeshTopology.Triangles, 0);
mMesh.RecalculateBounds ();
Update mesh each frame
private void UpdateVertices ()
{
var vertices = mMesh.vertices;
for (var i = 0; i < vertices.Length; i++) {
var v = transform.position;
var segInx = i / 2;
var ratio = (SEGMENTS_COUNT - segInx * 1f) / SEGMENTS_COUNT;
var seg = mMoveDir.normalized * segInx * WIDTH / 6f;
var spine = Mathf.Sin (ratio * Mathf.PI * 2.5f + Time.time * 6) * Vector3.left * WIDTH * 1.5f;
var wave = Vector3.left * WIDTH * Mathf.Min (1f, ratio + 0.5f);
if (i % 2 == 0) {
v = transform.position - seg - wave + spine;
} else {
v = transform.position - seg + wave + spine;
}
vertices [i] = v;
}
mMesh.vertices = vertices;
}
Render the mesh with shader
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_MaskTex("Mask",2D) = "white"{}
_MaskTex2("Mask2",2D) = "white"{}
}
_MaskTex flowing texture for simulating dynamic fires
_MaskTex2 static texture for masking the incinerate overall shape
Full version of source code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class IncinerateScript : MonoBehaviour
{
public Transform Target;
private static int SEGMENTS_COUNT = 21;
private static int WIDTH = 50;
private static int SPEED = 10;
private MeshFilter mMeshFilter;
private Mesh mMesh;
private Vector3 mMoveDir;
private bool mIsInit;
void Start ()
{
mMeshFilter = GetComponent<MeshFilter> ();
if (mMeshFilter != null) {
InitVertices (mMeshFilter);
mIsInit = true;
}
}
void Update ()
{
if (Target)
mMoveDir = Target.position - transform.position;
if (mIsInit)
UpdateVertices ();
}
private void InitVertices (MeshFilter meshFilter)
{
var vertices = new Vector3[(SEGMENTS_COUNT + 1) * 2];
var uvs = new Vector2[vertices.Length];
var indices = new int[SEGMENTS_COUNT * 2 * 3];
for (var i = 0; i < vertices.Length; i++) {
vertices [i] = transform.position;
}
for (var i = 0; i < uvs.Length; i++) {
float x = 0, y = i / (float)SEGMENTS_COUNT;
if (i % 2 == 0) {
x = 0;
} else {
x = 1;
}
uvs [i] = new Vector2 (x, y);
}
for (int i = 0; i < SEGMENTS_COUNT; i++) {
indices [6 * i + 0] = 0 + i * 2;
indices [6 * i + 1] = 1 + i * 2;
indices [6 * i + 2] = 2 + i * 2;
indices [6 * i + 3 + 0] = 2 + i * 2;
indices [6 * i + 3 + 1] = 1 + i * 2;
indices [6 * i + 3 + 2] = 3 + i * 2;
}
mMesh = meshFilter.mesh = new Mesh ();
mMesh.vertices = vertices;
mMesh.uv = uvs;
mMesh.SetIndices (indices, MeshTopology.Triangles, 0);
mMesh.RecalculateBounds ();
}
private void UpdateVertices ()
{
var vertices = mMesh.vertices;
for (var i = 0; i < vertices.Length; i++) {
var v = transform.position;
var segInx = i / 2;
var ratio = (SEGMENTS_COUNT - segInx * 1f) / SEGMENTS_COUNT;
var seg = mMoveDir.normalized * segInx * WIDTH / 6f;
var spine = Mathf.Sin (ratio * Mathf.PI * 2.5f + Time.time * 6) * Vector3.left * WIDTH * 1.5f;
var wave = Vector3.left * WIDTH * Mathf.Min (1f, ratio + 0.5f);
if (i % 2 == 0) {
v = transform.position - seg - wave + spine;
} else {
v = transform.position - seg + wave + spine;
}
vertices [i] = v;
}
mMesh.vertices = vertices;
}
}
and the shader:
Shader "Suntabu/Incinerate"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_MaskTex("Mask",2D) = "white"{}
_MaskTex2("Mask2",2D) = "white"{}
}
SubShader
{
Tags { "RenderType"="Transparent" }
LOD 100 Cull off
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float2 uv2 : TEXCOORD1;
float2 uv3 : TEXCOORD2;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _MaskTex;
float4 _MaskTex_ST;
sampler2D _MaskTex2;
float4 _MaskTex2_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex) + _Time.xy;
o.uv2 = TRANSFORM_TEX(v.uv, _MaskTex) - _Time.xy;
o.uv3 = TRANSFORM_TEX(v.uv, _MaskTex2);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
fixed4 mask = tex2D(_MaskTex,i.uv2);
fixed4 mask2 = tex2D(_MaskTex2,i.uv3);
return fixed4(col.rgb,mask.a) *mask2.a * 1.3;
}
ENDCG
}
}
}