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 { struct vsIn {
float4 pos : SV_Position; float4 pos : SV_Position;
float3 norm : NORMAL0; float3 norm : NORMAL0;
// float4 col : COLOR0; float4 col : COLOR0;
}; };
struct psIn { struct psIn {
float4 pos : SV_POSITION; float4 pos : SV_POSITION;
float4 color : COLOR0; float4 color : COLOR0;
float3 normal : NORMAL0;
float3 world_pos : WORLD;
float3 cam_pos : TEXCOORD1;
float3 cam_dir : TEXCOORD2;
uint view_id : SV_RenderTargetArrayIndex; uint view_id : SV_RenderTargetArrayIndex;
}; };
@ -25,26 +21,23 @@ psIn vs(vsIn input, uint id : SV_InstanceID) {
o.view_id = id % sk_view_count; o.view_id = id % sk_view_count;
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; 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.pos = mul(float4(world, 1), sk_viewproj[o.view_id]);
o.world_pos = world;
// flip input normals to treat backfaces like normal float3 normal = normalize(mul(input.norm, (float3x3)sk_inst[id].world));
o.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; return o;
} }
float4 ps(psIn input) : SV_TARGET { float4 ps(psIn input) : SV_TARGET {
float3 view_dir = normalize(input.world_pos - input.cam_pos); return input.color;
// 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);
} }

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

Binary file not shown.

View file

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

View file

@ -49,32 +49,23 @@ todo
target audience/experience target audience/experience
~13-18-year-olds who enjoy competitive, skill-based, solo gaming ~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 seated experience on the Meta Quest 3/3S
moderate learning curve 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 stylized visuals
fun to play daily for a couple weeks or so fun to play daily for a couple weeks or so
playable with a half engaging vid or conversation in the background
my tempo start outside of the box (about 6-9 segments long)
on l_con tap (a) btn to step, and repeatedly to set tempo snake is moving like normal but is parented to an offset pose that keeps their head centered
once a tempo has been set you can stop tapping and you'll coast at that tempo so you can easily get a feel for how the snake moves
if you tap again it resets the tempo latch, so if you just tapped once it's an easy way to pause (sort of like writhing around in zero g)
and if you keep tapping you can control your pace and find a new tempo *dreamlike*
wake up in box
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
this makes it easier to play alongside a video, as your attention span can drift in and out mount/unmount box for orbital_view || handheld || desk/space view
and you can take tougher parts slower if placed within 15 degree cone of head view then
mount (zero out relative x offset)
bug(s) 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.Collections.Generic;
using System.Threading;
using StereoKit; using StereoKit;
namespace snake; namespace snake;
@ -10,7 +9,6 @@ static class Arts
static Dictionary<string, Mesh> meshes = new(); static Dictionary<string, Mesh> meshes = new();
static Material mat_mono = new Material("mono.hlsl"); static Material mat_mono = new Material("mono.hlsl");
static Material mat_unlit = new Material("unlit.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_backbox = new Material("backbox.hlsl");
static Material mat_justcolor = new Material("justcolor.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.Transparency = Transparency.Add;
mat_backbox.DepthTest = DepthTest.LessOrEq; mat_backbox.FaceCull = Cull.Front;
mat_backbox.DepthWrite = false;
mat_box.Chain = mat_backbox;
} }
public static void Frame() public static void Frame()
@ -66,16 +59,13 @@ static class Arts
// box // box
Hierarchy.Push(Mono.box_pose.ToMatrix(Mono.box_scale)); Hierarchy.Push(Mono.box_pose.ToMatrix(Mono.box_scale));
// meshes["InsideOut"].Draw(mat_unlit, Matrix.Identity); meshes["InsideOut"].Draw(mat_unlit, Matrix.Identity);
meshes["InsideOut"].Draw( meshes["InsideOut"].Draw(mat_backbox, Matrix.Identity);
mat_box,
Matrix.Identity
);
meshes["Corrugation"].Draw( meshes["Corrugation"].Draw(
Mono.in_dist.state ? mat_justcolor : mat_unlit, Mono.in_dist.state ? mat_justcolor : mat_unlit,
Matrix.Identity 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( meshes["Hanging"].Draw(
mat_unlit, mat_unlit,
@ -88,14 +78,14 @@ static class Arts
meshes["uiPlay"].Draw( meshes["uiPlay"].Draw(
mat_unlit, mat_unlit,
Matrix.TR( 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) Quat.FromAngles(90, 0, 0)
) )
); );
meshes["uiCursor"].Draw( meshes["uiCursor"].Draw(
mat_unlit, mat_unlit,
Matrix.TR( 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) Quat.FromAngles(90, 0, 0)
) )
); );
@ -103,7 +93,7 @@ static class Arts
// snake // snake
float snake_t = headmove.state ? Maths.u_clamp(Maths.smooth_stop((float)Mono.step_t) * 3.0f) : 1.0f; 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) if (!food_next)
{ {
meshes["Tongue"].Draw( meshes["Tongue"].Draw(
@ -159,24 +149,8 @@ static class Arts
// holes // holes
foreach (KeyValuePair<XYZi, XYZi> hole in Mono.holes) foreach (KeyValuePair<XYZi, XYZi> hole in Mono.holes)
{ {
Vec3 hole_normal = V.XYZ( meshes["Hole"].Draw(
Maths.abs(hole.Value.x) * Maths.sign(hole.Key.x), mat_unlit,
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,
Matrix.TRS( Matrix.TRS(
hole.Key.ToVec3, hole.Key.ToVec3,
Quat.LookDir(hole.Value.ToVec3), Quat.LookDir(hole.Value.ToVec3),
@ -195,18 +169,13 @@ static class Arts
10 * Time.Stepf 10 * Time.Stepf
); );
} }
if (!Mono.eaten_latch.state) meshes["Food"].Draw(
{ mat_mono,
float food_t = Mono.eaten_latch.delta == -1 ? Maths.smooth_stop((float)Mono.step_t) : 1; Matrix.TR(
meshes["Food"].Draw( Mono.food.ToVec3,
mat_mono, food_ori
Matrix.TRS( )
Mono.food.ToVec3, );
food_ori,
food_t
)
);
}
Hierarchy.Pop(); 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 Pose box_pose = new(0, -3 * U.cm, -10 * U.cm);
public static float box_scale = 1.333f * 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> 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), 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); tail_fill = new(SD_X, SD_Y, SD_Z, -1);
public static XYZi[] snake = new XYZi[ public static XYZi[] snake = new XYZi[
@ -27,8 +27,7 @@ static class Mono
public static XYZi snake_dir = new(0, 0, 1); public static XYZi snake_dir = new(0, 0, 1);
public static DeltaBool in_box = new(true); public static DeltaBool in_box = new(true);
public static Dictionary<XYZi, XYZi> holes = new(); public static Dictionary<XYZi, XYZi> holes = new();
public static XYZi food = new(2, 0, 0); // [!] start random to keep new game fresh? public static XYZi food = new(2, 0, 0);
public static DeltaBool eaten_latch = new(false);
public static double eat_timestamp = 0.0; public static double eat_timestamp = 0.0;
public enum BoxMode public enum BoxMode
@ -64,11 +63,8 @@ static class Mono
if (Input.Key(Key.MouseCenter).IsActive()) if (Input.Key(Key.MouseCenter).IsActive())
{ {
float sx = Input.Mouse.posChange.x; float sx = Input.Mouse.posChange.x;
Renderer.CameraRoot *= Matrix.R( float ssx = Maths.smooth_start(sx);
Quat.FromAngles(0, -sx * 180.0f * Time.Stepf, 0) box_pose.orientation *= 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);
} }
} }
else else
@ -106,13 +102,7 @@ static class Mono
public static void Step() public static void Step()
{ {
// eat tail if (s_array[snake[0] + snake_dir] > -1)
if (snake[0] + snake_dir == snake[snake_len-1])
{
snake_len--;
grow_buffer = 0;
}
else if (s_array[snake[0] + snake_dir] > -1)
{ {
// lose condition // lose condition
bool stuck = true; bool stuck = true;
@ -130,8 +120,14 @@ static class Mono
return; return;
} }
bool in_or_around_box = s_array.InRange(snake[0] + snake_dir); XYZi next_pos = snake[0] + snake_dir;
if (!in_or_around_box) 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; return;
} }
@ -161,7 +157,7 @@ static class Mono
snake[0] += snake_dir; 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 if (in_box.delta != 0) // 1 just in -1 just out
{ {
holes.Add(snake[0], snake_dir); holes.Add(snake[0], snake_dir);
@ -179,62 +175,51 @@ static class Mono
} }
// eat // eat
if (!eaten_latch.state) if (food == snake[0])
{ {
eaten_latch.Step(food == snake[0]); eat_timestamp = Time.Total;
if (eaten_latch.delta == +1) grow_buffer += 3;
{
eat_timestamp = Time.Total;
grow_buffer += 3;
SFX.crisp_nom.PlayBox(snake[0]); Feed();
} SFX.crisp_nom.PlayBox(snake[0]);
} else {
(bool viable, XYZi cell) = Feed();
eaten_latch.Step(!viable);
if (eaten_latch.delta == -1)
{
food = cell;
}
} }
} }
static (bool, XYZi) Feed() static void Feed()
{ {
// [!] handle out of the box exception on tail head_fill.Clear(-1);
// by making the spatial arrays encapsulate the layer outside of the box Gas(head_fill, snake[0]);
// handle out of the box exception on tail
tail_fill.Clear(-1); tail_fill.Clear(-1);
Gas(tail_fill, snake[snake_len - 1]); Gas(tail_fill, snake[snake_len - 1]);
// step from head using directions towards the tail XYZi farthest_cell = new XYZi(0, 0, 0);
// and stop at either 1 cell away from the tail or 5 spaces away from the head int farthest_dist = 0;
bool viable = false; // [*] design configurable (we basically want it to be as far away as it can reasonably be)
XYZi cell = snake[0]; const int max_dist = 6;
for (int step = 0; step < 5; step++) // [!] 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; for (int sy = -s_array.Yslen; s_array.InY(sy); sy++)
XYZi min_cell = new();
int[] dir_indices = GetShuffledIndices(directions);
for (int i = 0; i < directions.Length; i++)
{ {
XYZi dir = directions[dir_indices[i]]; for (int sz = -s_array.Zslen; s_array.InZ(sz); sz++)
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))
{ {
min_dist = tail_dist; XYZi sv = new XYZi(sx, sy, sz);
min_cell = dir_cell; int dist = head_fill[sv];
viable = true; 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 // 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 // directions for moving in the grid
static readonly XYZi[] directions = new XYZi[] static readonly XYZi[] directions = new XYZi[]
{ {
@ -301,14 +269,4 @@ static class Mono
new XYZi(+1, +1, -1), new XYZi(+1, +1, -1),
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 else
{ {
// Hand r_hand = Input.Hand(Handed.Right); // Hand r_hand = Input.Hand(Handed.Right);
// [!] hand input simulates controller...
Controller r_con = Input.Controller(Handed.Right); 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 con_tracked = r_con.trackedPos > TrackState.Lost;
// bool hand_tracked = Input.HandSource(Handed.Right) > HandSource.None; Input.HandVisible(Handed.Max, !con_tracked);
Input.HandVisible(Handed.Max, false); // hide hands
if (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; Vec2 stick = r_con.stick;
Quat stick_rot = Quat.FromAngles(stick.y * -90, 0, stick.x * +90); Quat stick_rot = Quat.FromAngles(stick.y * -90, 0, stick.x * +90);
float stick_sign = r_con.IsStickClicked ? -1 : +1; float stick_sign = r_con.IsStickClicked ? -1 : +1;