Compare commits

..

No commits in common. "6d495d8307f1e9bc79d9284c9e7dcb155100e60e" and "352535eaa2d6d0819383c71cd1cb705334efef3d" have entirely different histories.

7 changed files with 89 additions and 181 deletions

View file

@ -8,15 +8,11 @@ float4 color;
struct vsIn {
float4 pos : SV_Position;
float3 norm : NORMAL0;
// float4 col : COLOR0;
float4 col : COLOR0;
};
struct psIn {
float4 pos : SV_POSITION;
float4 color : COLOR0;
float3 normal : NORMAL0;
float3 world_pos : WORLD;
float3 cam_pos : TEXCOORD1;
float3 cam_dir : TEXCOORD2;
uint view_id : SV_RenderTargetArrayIndex;
};
@ -25,26 +21,23 @@ psIn vs(vsIn input, uint id : SV_InstanceID) {
o.view_id = id % sk_view_count;
id = id / sk_view_count;
o.cam_pos = sk_camera_pos[o.view_id].xyz;
o.cam_dir = sk_camera_dir[o.view_id].xyz;
float3 world = mul(float4(input.pos.xyz, 1), sk_inst[id].world).xyz;
o.pos = mul(float4(world, 1), sk_viewproj[o.view_id]);
o.world_pos = world;
// flip input normals to treat backfaces like normal
o.normal = normalize(mul(-input.norm, (float3x3)sk_inst[id].world));
float3 normal = normalize(mul(input.norm, (float3x3)sk_inst[id].world));
o.color = color * sk_inst[id].color;
float3 norm_shade = float3(0.5) + (normal * 0.5);
// float3 norm_shade = float3(0.5) + (norm_color * 0.5);
float3 view_dir = normalize(world - sk_camera_pos[o.view_id]);
// o.color = norm_shaed * clamp(dot(o.normal, -view_dir), 0, 0.2);
// rim lighting
float rim = 1.0 - saturate(dot(normalize(view_dir), normal));
o.color = float4(norm_shade * rim * rim * 0.2, 1);
return o;
}
float4 ps(psIn input) : SV_TARGET {
float3 view_dir = normalize(input.world_pos - input.cam_pos);
// Fresnel effect calculation
float fresnel = 1.0 - saturate(dot(-view_dir, input.normal));
fresnel = pow(fresnel, 5.0); // Adjust power for different falloff rates
float value = 0.5;
return float4(fresnel * input.color.rgb * value, input.color.a);
return input.color;
}

BIN
Assets/meshes/assets.glb (Stored with Git LFS)

Binary file not shown.

View file

@ -2,8 +2,8 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dofdev.snake"
android:versionCode="17"
android:versionName="1.27"
android:versionCode="11"
android:versionName="1.15"
android:installLocation="auto"
>
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="32" />

View file

@ -49,32 +49,23 @@ todo
target audience/experience
~13-18-year-olds who enjoy competitive, skill-based, solo gaming
sprinkle in some pop culture relevant to that age demographic that's in good taste to target interests
seated experience on the Meta Quest 3/3S
moderate learning curve
start outside of the box (about 6-9 segments long)
snake is moving like normal but is parented to an offset pose that keeps their head centered
so you can easily get a feel for how the snake moves
(sort of like writhing around in zero g)
*dreamlike*
wake up in box
stylized visuals
fun to play daily for a couple weeks or so
playable with a half engaging vid or conversation in the background
my tempo
on l_con tap (a) btn to step, and repeatedly to set tempo
once a tempo has been set you can stop tapping and you'll coast at that tempo
if you tap again it resets the tempo latch, so if you just tapped once it's an easy way to pause
and if you keep tapping you can control your pace and find a new tempo
long press (a) or tap (b) btn
to switch into coasting on tempo, in order to maintain the most control in ramping up and down speed with tapping without the tempo kicking in prematurely
start outside of the box (about 6-9 segments long)
snake is moving like normal but is parented to an offset pose that keeps their head centered
so you can easily get a feel for how the snake moves
(sort of like writhing around in zero g)
*dreamlike*
wake up in box
this makes it easier to play alongside a video, as your attention span can drift in and out
and you can take tougher parts slower
mount/unmount box for orbital_view || handheld || desk/space view
if placed within 15 degree cone of head view then
mount (zero out relative x offset)
bug(s)
...
food.spawn fails to find a spot and defaults to zero
```

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Threading;
using StereoKit;
namespace snake;
@ -10,7 +9,6 @@ static class Arts
static Dictionary<string, Mesh> meshes = new();
static Material mat_mono = new Material("mono.hlsl");
static Material mat_unlit = new Material("unlit.hlsl");
static Material mat_box = new Material("unlit.hlsl");
static Material mat_backbox = new Material("backbox.hlsl");
static Material mat_justcolor = new Material("justcolor.hlsl");
@ -31,13 +29,8 @@ static class Arts
}
}
mat_backbox.FaceCull = Cull.Front;
mat_backbox.Transparency = Transparency.Add;
mat_backbox.DepthTest = DepthTest.LessOrEq;
mat_backbox.DepthWrite = false;
mat_box.Chain = mat_backbox;
mat_backbox.FaceCull = Cull.Front;
}
public static void Frame()
@ -66,16 +59,13 @@ static class Arts
// box
Hierarchy.Push(Mono.box_pose.ToMatrix(Mono.box_scale));
// meshes["InsideOut"].Draw(mat_unlit, Matrix.Identity);
meshes["InsideOut"].Draw(
mat_box,
Matrix.Identity
);
meshes["InsideOut"].Draw(mat_unlit, Matrix.Identity);
meshes["InsideOut"].Draw(mat_backbox, Matrix.Identity);
meshes["Corrugation"].Draw(
Mono.in_dist.state ? mat_justcolor : mat_unlit,
Matrix.Identity
);
if (Mono.in_cone.state && Mono.box_mode == Mono.BoxMode.Hold || Mono.box_mode == Mono.BoxMode.Mount)
if (Mono.in_cone.state)
{
meshes["Hanging"].Draw(
mat_unlit,
@ -88,14 +78,14 @@ static class Arts
meshes["uiPlay"].Draw(
mat_unlit,
Matrix.TR(
V.XYZ(0, 0, Mono.SD_Z - 0.5f + 0.1f),
V.XYZ(0, 0, Mono.SD_Z + 0.5f + 0.1f),
Quat.FromAngles(90, 0, 0)
)
);
meshes["uiCursor"].Draw(
mat_unlit,
Matrix.TR(
V.XYZ(0, 0, Mono.SD_Z - 0.5f + 0.2f),
V.XYZ(0, 0, Mono.SD_Z + 0.5f + 0.2f),
Quat.FromAngles(90, 0, 0)
)
);
@ -103,7 +93,7 @@ static class Arts
// snake
float snake_t = headmove.state ? Maths.u_clamp(Maths.smooth_stop((float)Mono.step_t) * 3.0f) : 1.0f;
bool food_next = !Mono.eaten_latch.state && (Mono.snake[0] + Mono.snake_dir) == Mono.food;
bool food_next = (Mono.snake[0] + Mono.snake_dir) == Mono.food;
if (!food_next)
{
meshes["Tongue"].Draw(
@ -159,24 +149,8 @@ static class Arts
// holes
foreach (KeyValuePair<XYZi, XYZi> hole in Mono.holes)
{
Vec3 hole_normal = V.XYZ(
Maths.abs(hole.Value.x) * Maths.sign(hole.Key.x),
Maths.abs(hole.Value.y) * Maths.sign(hole.Key.y),
Maths.abs(hole.Value.z) * Maths.sign(hole.Key.z)
);
// Vec3 hole_world_normal = Mono.box_pose.orientation * hole_normal;
// Vec3 hole_world = Mono.box_pose.ToMatrix(Mono.box_scale) * V.XYZ(
// hole.Key.x - hole.Value.x * 0.5f,
// hole.Key.y - hole.Value.y * 0.5f,
// hole.Key.z - hole.Value.z * 0.5f
// );
// Vec3 cam_dir = (Rig.head.orientation * Vec3.Forward);
// Vec3 hole_view_dir = Vec3.Direction(hole_world, Rig.head.position);
// bool back_hole = Vec3.Dot(hole_world_normal, hole_view_dir) < 0.0;
bool hole_flip = Vec3.Dot(hole_normal, hole.Value.ToVec3) < 0.0;
meshes[hole_flip ? "Hole" : "HoleFlip"].Draw(
mat_box,
meshes["Hole"].Draw(
mat_unlit,
Matrix.TRS(
hole.Key.ToVec3,
Quat.LookDir(hole.Value.ToVec3),
@ -195,18 +169,13 @@ static class Arts
10 * Time.Stepf
);
}
if (!Mono.eaten_latch.state)
{
float food_t = Mono.eaten_latch.delta == -1 ? Maths.smooth_stop((float)Mono.step_t) : 1;
meshes["Food"].Draw(
mat_mono,
Matrix.TRS(
Mono.food.ToVec3,
food_ori,
food_t
)
);
}
meshes["Food"].Draw(
mat_mono,
Matrix.TR(
Mono.food.ToVec3,
food_ori
)
);
Hierarchy.Pop();

View file

@ -13,10 +13,10 @@ static class Mono
public static Pose box_pose = new(0, -3 * U.cm, -10 * U.cm);
public static float box_scale = 1.333f * U.cm;
public const int SD_X = 3, SD_Y = 2, SD_Z = 3;
public const int SD_X = 2, SD_Y = 1, SD_Z = 2;
public static SpatialArray<int>
box_space = new(SD_X-1, SD_Y-1, SD_Z-1, -1),
s_array = new(SD_X, SD_Y, SD_Z, -1),
head_fill = new(SD_X, SD_Y, SD_Z, -1),
tail_fill = new(SD_X, SD_Y, SD_Z, -1);
public static XYZi[] snake = new XYZi[
@ -27,8 +27,7 @@ static class Mono
public static XYZi snake_dir = new(0, 0, 1);
public static DeltaBool in_box = new(true);
public static Dictionary<XYZi, XYZi> holes = new();
public static XYZi food = new(2, 0, 0); // [!] start random to keep new game fresh?
public static DeltaBool eaten_latch = new(false);
public static XYZi food = new(2, 0, 0);
public static double eat_timestamp = 0.0;
public enum BoxMode
@ -64,11 +63,8 @@ static class Mono
if (Input.Key(Key.MouseCenter).IsActive())
{
float sx = Input.Mouse.posChange.x;
Renderer.CameraRoot *= Matrix.R(
Quat.FromAngles(0, -sx * 180.0f * Time.Stepf, 0)
);
// orbital_view
box_pose.position = Input.Head.position + Input.Head.orientation * V.XYZ(0, -0 * U.cm, -10 * U.cm);
float ssx = Maths.smooth_start(sx);
box_pose.orientation *= Quat.FromAngles(0, sx * 180.0f * Time.Stepf, 0);
}
}
else
@ -106,13 +102,7 @@ static class Mono
public static void Step()
{
// eat tail
if (snake[0] + snake_dir == snake[snake_len-1])
{
snake_len--;
grow_buffer = 0;
}
else if (s_array[snake[0] + snake_dir] > -1)
if (s_array[snake[0] + snake_dir] > -1)
{
// lose condition
bool stuck = true;
@ -130,8 +120,14 @@ static class Mono
return;
}
bool in_or_around_box = s_array.InRange(snake[0] + snake_dir);
if (!in_or_around_box)
XYZi next_pos = snake[0] + snake_dir;
XYZi inset_pos = new(
next_pos.x - Maths.sign(next_pos.x),
next_pos.y - Maths.sign(next_pos.y),
next_pos.z - Maths.sign(next_pos.z)
);
bool around_box = s_array.InRange(inset_pos);
if (!around_box)
{
return;
}
@ -161,7 +157,7 @@ static class Mono
snake[0] += snake_dir;
}
in_box.Step(box_space.InRange(snake[0]));
in_box.Step(s_array.InRange(snake[0]));
if (in_box.delta != 0) // 1 just in -1 just out
{
holes.Add(snake[0], snake_dir);
@ -179,62 +175,51 @@ static class Mono
}
// eat
if (!eaten_latch.state)
if (food == snake[0])
{
eaten_latch.Step(food == snake[0]);
if (eaten_latch.delta == +1)
{
eat_timestamp = Time.Total;
grow_buffer += 3;
eat_timestamp = Time.Total;
grow_buffer += 3;
SFX.crisp_nom.PlayBox(snake[0]);
}
} else {
(bool viable, XYZi cell) = Feed();
eaten_latch.Step(!viable);
if (eaten_latch.delta == -1)
{
food = cell;
}
Feed();
SFX.crisp_nom.PlayBox(snake[0]);
}
}
static (bool, XYZi) Feed()
static void Feed()
{
// [!] handle out of the box exception on tail
// by making the spatial arrays encapsulate the layer outside of the box
head_fill.Clear(-1);
Gas(head_fill, snake[0]);
// handle out of the box exception on tail
tail_fill.Clear(-1);
Gas(tail_fill, snake[snake_len - 1]);
// step from head using directions towards the tail
// and stop at either 1 cell away from the tail or 5 spaces away from the head
bool viable = false;
XYZi cell = snake[0];
for (int step = 0; step < 5; step++)
XYZi farthest_cell = new XYZi(0, 0, 0);
int farthest_dist = 0;
// [*] design configurable (we basically want it to be as far away as it can reasonably be)
const int max_dist = 6;
// [!] given how it loops over the space it directionally biases the results
for (int sx = -s_array.Xslen; s_array.InX(sx); sx++)
{
int min_dist = 100;
XYZi min_cell = new();
int[] dir_indices = GetShuffledIndices(directions);
for (int i = 0; i < directions.Length; i++)
for (int sy = -s_array.Yslen; s_array.InY(sy); sy++)
{
XYZi dir = directions[dir_indices[i]];
XYZi dir_cell = cell + dir;
int tail_dist = tail_fill[dir_cell];
if (tail_dist > 1 && tail_dist < min_dist && box_space.InRange(dir_cell))
for (int sz = -s_array.Zslen; s_array.InZ(sz); sz++)
{
min_dist = tail_dist;
min_cell = dir_cell;
viable = true;
XYZi sv = new XYZi(sx, sy, sz);
int dist = head_fill[sv];
bool good_dist = dist > farthest_dist && dist < max_dist;
bool tail_access = tail_fill[sv] > 0; // help ensure completability
bool snake_free = s_array[sv] == -1;
if (good_dist && tail_access && snake_free)
{
farthest_dist = dist;
farthest_cell = sv;
}
}
}
cell = min_cell;
// if (min_dist <= 1)
// {
// break;
// }
}
return (viable, cell);
food = farthest_cell;
}
// space fill algorithm
@ -263,23 +248,6 @@ static class Mono
}
}
static int[] GetShuffledIndices<T>(T[] array)
{
int[] indices = new int[array.Length];
for (int i = 0; i < array.Length; i++)
{
indices[i] = i;
}
for (int i = indices.Length - 1; i > 0; i--)
{
int j = System.Random.Shared.Next(i + 1);
int temp = indices[i];
indices[i] = indices[j];
indices[j] = temp;
}
return indices;
}
// directions for moving in the grid
static readonly XYZi[] directions = new XYZi[]
{
@ -301,14 +269,4 @@ static class Mono
new XYZi(+1, +1, -1),
new XYZi(+1, +1, +1)
};
static XYZi InsetCell(XYZi cell)
{
// [!] don't inset past 0
return new(
cell.x - Maths.sign(cell.x),
cell.y - Maths.sign(cell.y),
cell.z - Maths.sign(cell.z)
);
}
}

View file

@ -39,17 +39,14 @@ static class Rig
else
{
// Hand r_hand = Input.Hand(Handed.Right);
// [!] hand input simulates controller...
Controller r_con = Input.Controller(Handed.Right);
btn_trigger.Step(r_con.trigger > 0.5f);
btn_grip.Step(r_con.grip > 0.5f);
bool con_tracked = r_con.trackedPos > TrackState.Lost;
// bool hand_tracked = Input.HandSource(Handed.Right) > HandSource.None;
Input.HandVisible(Handed.Max, false); // hide hands
Input.HandVisible(Handed.Max, !con_tracked);
if (con_tracked)
{
btn_trigger.Step(r_con.trigger > 0.5f);
btn_grip.Step(r_con.grip > 0.5f);
Vec2 stick = r_con.stick;
Quat stick_rot = Quat.FromAngles(stick.y * -90, 0, stick.x * +90);
float stick_sign = r_con.IsStickClicked ? -1 : +1;