|
google play? , , - ?
, : - . . , devlog.
-.
? . , ( , ) . ? ? ? ? , , . !
, . . , , .
, . Crimsonland . gui . :
: , .
: (), .
. . :
1. ;
2. ;
3. .
. , .
:
. , .
Todo: , / .
Unity3D, .
BoxCollider2D, RigidBody2D CircleCollider2D (, ). , , , RigidBody2D, CircleCollider2D TrailRenderer .
Clock, (, ) , Time.DeltaTime.
using UnityEngine;
using System.Collections;
public class Clock : MonoBehaviour {
[SerializeField, Range(0, 2)] float stepDuration;
[SerializeField] AnimationCurve stepCurve;
float time = -1;
float timeRatio = 0;
float defaultFixedDeltaTime = 0;
static Clock instance;
public static Clock Instance { get { return instance; } }
void Start() {
instance = this;
defaultFixedDeltaTime = Time.fixedDeltaTime;
}
void OnDestroy() {
if (instance == this)
instance = null;
}
public bool Paused {
get { return time < 0; }
}
public float DeltaTime {
get { return
timeRatio * Time.deltaTime; }
}
public float FixedDeltaTime {
get { return
timeRatio * Time.fixedDeltaTime; }
}
public void Play() {
if (!Paused)
return;
time = 0;
timeRatio = Mathf.Max(0, stepCurve.Evaluate(0));
UpdatePhysicSpeed();
}
public void Update() {
if (Paused)
return;
time = Mathf.Min(time + Time.unscaledDeltaTime, stepDuration);
if (time >= stepDuration) {
timeRatio = 0;
time = -1;
UpdatePhysicSpeed();
return;
}
timeRatio = Mathf.Max(0, stepCurve.Evaluate(time / stepDuration));
UpdatePhysicSpeed();
}
void UpdatePhysicSpeed() {
Time.timeScale = timeRatio;
Time.fixedDeltaTime = defaultFixedDeltaTime * timeRatio;
}
}
, :
. , , :
:
:
:
Todo: .
, :
, . - :
, :
void OnCollisionEnter2D(Collision2D coll) {
int layer = 1 << coll.gameObject.layer;
if (layer == wall.value)
Destroy(gameObject);
else if (layer == human.value) {
Destroy(gameObject);
var humanBody = coll.gameObject.GetComponent();
if (humanBody != null)
humanBody.Kill();
return;
} else if (layer == wallMirror.value) {
Vector2 normal = Vector2.zero;
foreach (var contact in coll.contacts)
normal += contact.normal;
direction = Vector2.Reflect(direction, normal);
}
}
, .
Clock: stepDuration , stepCurve. .
Clock.cs
, / ( 1). "" , (.. "" ). , /.
, .
, , . , , . playtest':
, !
Todo: , .
"" , .
: , , .
. , , . , , .
. . , , .
360 , , . , " ", . - , , . : " !", Hotline miami ( !). : " !".
, : , .
(, !) , !
Todo: .
! Wildfire worlds, . , , :
, 3 , 3 . , . , , .
: . , .
: . . , , . , ...
. , - , , . 3 ? , ! , , low poly. , , .
, . , , , . !
Todo: .
. 2, :
, :
, "", , . , , , .
.
, :
, :
.
: , , . , . , . : , ?
,
, . 3D.
, 3D . , 2D.
:
, :
. 3 :
, .. x y, .
:
, , .
AnglesCache :
namespace ObstacleGenerators {
public class AnglesCache {
public Vector3[] GetAngles(int sides);
}
}
, 3 , ( , , ..). , , , .
: , , , :
, ..
. :
, ZTest On (LEqual) Less. . , :
ZTest'
, :
, , uv . ( )
PolygonCollider2D .
: lowpoly .
Todo: .
, :
, ..
:
" ?" . " Unity3D !".
, . Shadow mapping. : , , , , - . , ( z-buffer' ). . , .
.. "pixel perfect", , , . , , . , , .
, . "Shadow volume" , .
, .
, 1 "" ( front ) "" ( back ) . , (, front back - , z-buffer) .
, , , , , , . , !
Todo: .
, - , GL . , .
32 . 4 , :
32 , 32 , 96 .
, 96*2 = 192 384 . .
, : , (front), (back). 2 , 4 (2 ), Cull Back Cull Front.
32 * 4 = 128 , 256 512 . .
, .
.
: , (, ). , :
:
( ) , .
, !
, , :
, : 100-200 draw call' . , CPU. , , ? :)
Todo: CPU.
.
:
1.1. ;
1.2. ;
1.3. ;
1.4. , , (lightToShadowIndex);
1.5.1 , , (shadowToLightIndex);
:
2.1. lightToShadowIndex shadowToLightIndex 2 (, 4-, 2 , 2 , );
:
3.1 shadowToLightIndex lightToShadowIndex 2 ;
:
, ( ).
: , .
, . , 60fps 10 , 600 . ( 6 10 ).
Todo: , 60fps nexus 5.
:
, . , .
:
. AnglesCache, . :
using UnityEngine;
using System.Collections.Generic;
namespace ObstacleGenerators {
public class AnglesCache {
List cache;
const int MAX_CACHE_SIZE = 100;
public AnglesCache () {
cache = new List(MAX_CACHE_SIZE);
for (int i = 0; i < MAX_CACHE_SIZE; ++i)
cache.Add(null);
}
public Vector2[] GetAngles(int sides) {
if (sides < 0)
return null;
if (sides > MAX_CACHE_SIZE)
return GenerateAngles(sides);
if (cache[sides] == null)
cache[sides] = GenerateAngles(sides);
return cache[sides];
}
public float AngleOffset {
get { return Mathf.PI * 0.25f; }
}
Vector2[] GenerateAngles(int sides) {
var result = new Vector2[sides];
float deltaAngle = 360.0f / sides;
float firstAngle = AngleOffset;
var matrix = Matrix4x4.TRS(Vector2.zero, Quaternion.Euler(0, 0, deltaAngle), Vector2.one);
var direction = new Vector2(Mathf.Cos(firstAngle), Mathf.Sin(firstAngle));
for (int i = 0; i < sides; ++i) {
result[i] = direction;
direction = matrix.MultiplyPoint3x4(direction);
}
return result;
}
}
}
:
( ). , c .
:
Transform.TransformPoint transform.localToWorldMatrix MultiplyPoint3x4.
Vector3 Vector2 ( , ), , :
Vector2 v2;
Vector3 v3;
// ,
v2.x = v3.x;
v2.y = v3.y;
//
v2.Set(v3.x, v3.y);
// ,
v2 = v3;
, , , .
:
. , , :
, . .
:
direction = lightPosition - obstacleCenter;
firstAngle = directionAngle - deltaAngle;
secondAngle = directionAngle + deltaAngle;
fromLightToShadow = Mathf.FloorToInt(firstAngle / pi2 * edges + edges) % edges;
fromShadowToLight = Mathf.FloorToInt(secondAngle / pi2 * edges + edges) % edges;
if (linesCache[fromLightToShadow].HalfPlainSign(lightPosition) < 0)
fromLightToShadow = (fromLightToShadow + 1) % edges;
if (linesCache[fromShadowToLight].HalfPlainSign(lightPosition) >= 0)
fromShadowToLight = (fromShadowToLight + 1) % edges;
. (Acos, Atan2) . . , . , :
.
bool CanUseFastSilhouette(Vector2 lightPosition) {
if (size.x != size.y || edgesList != null)
return false;
return (lightPosition - (Vector2)transform.position).sqrMagnitude > size.x * size.x;
}
bool FindSilhouetteEdges(Vector2 lightPosition, Vector3[] angles, out int fromLightToShadow, out int fromShadowToLight) {
if (CanUseFastSilhouette(lightPosition))
return FindSilhouetteEdgesFast(lightPosition, angles, out fromLightToShadow, out fromShadowToLight);
return FindSilhouetteEdges(lightPosition, out fromLightToShadow, out fromShadowToLight);
}
bool FindSilhouetteEdgesFast(Vector2 lightPosition, Vector3[] angles, out int fromLightToShadow, out int fromShadowToLight) {
Vector2 center = transform.position;
float radius = size.x;
Vector2 delta = center - lightPosition;
float deltaMagnitude = delta.magnitude;
float sin = radius / deltaMagnitude;
Vector2 direction = delta / deltaMagnitude;
float pi2 = Mathf.PI * 2.0f;
float directionAngle = Mathf.Atan2(-direction.y, -direction.x) - anglesCache.AngleOffset - transform.rotation.eulerAngles.z * Mathf.Deg2Rad;
float deltaAngle = Mathf.Acos(sin);
float firstAngle = ((directionAngle - deltaAngle) % pi2 + pi2) % pi2;
float secondAngle = ((directionAngle + deltaAngle) % pi2 + pi2) % pi2;
fromLightToShadow = Mathf.RoundToInt(firstAngle / pi2 * edges - 1 + edges) % edges;
fromShadowToLight = Mathf.RoundToInt(secondAngle / pi2 * edges - 1 + edges) % edges;
return true;
}
:
cpu, . , , 32 , , 42 36 ( 512 256 gpu).
:
, . "" . , .
:
x y ( ) c , .
bounding box:
Mesh.RecalculateBounds . AABB .
:
, , .
( )
( )
Bounding box ( )
, , .
, , . :)
, , , . , , :
:
, ! :)