Compare commits

...

30 commits

Author SHA1 Message Date
6d495d8307 input hand troubleshooting 2024-11-11 00:15:43 -05:00
56829852dc don't show hangin on float 2024-11-11 00:00:14 -05:00
5f49d94910 hole flip trick, with box mat chain 2024-11-10 23:49:00 -05:00
470fa38107 holes backface when box face does 2024-11-10 15:38:40 -05:00
2eb15dd992 simulator orbital view 2024-11-10 07:06:15 -05:00
a906d2bfdc drop fresnel intensity on box 2024-11-10 06:36:46 -05:00
cfbd489c22 fresnel effect 2024-11-10 06:34:40 -05:00
17f38b83bd polished hole 2024-11-10 06:34:31 -05:00
9aeafa7ba0 flip input normals to treat backfaces like normal 2024-11-10 05:31:31 -05:00
ffc9e3fecb proper settings for additive style mat 2024-11-10 05:24:37 -05:00
4a7f536999 draw hole backfaces 2024-11-10 05:24:13 -05:00
5b96fa3df3 version bump for refactor 2024-11-10 05:10:12 -05:00
a81cc0073a stash inset cell function away 2024-11-10 05:08:57 -05:00
5b0a096502 don't move if next pos is not in or around box 2024-11-10 05:08:05 -05:00
c1f0bcc956 refactor for around box validity 2024-11-10 05:07:28 -05:00
3fdea357fc my tempo idea 2024-11-10 04:48:52 -05:00
6121d333cf subtarget 2024-11-10 04:48:36 -05:00
11ac4207e9 step if tracked 2024-11-10 04:48:05 -05:00
196ba2955a bug killed todo done 2024-11-10 04:23:22 -05:00
7dc2cfe6a4 version increment 2024-11-10 04:21:58 -05:00
b6e20580e6 lerp food in 2024-11-10 04:21:17 -05:00
2900070ea9 eaten latch delta bool 2024-11-10 04:20:58 -05:00
9b5a17cb0c undocument bug 2024-11-10 04:03:23 -05:00
c87d56ef43 unstretch nose 2024-11-10 04:02:32 -05:00
350e4f734b hide food on eaten latch 2024-11-10 03:58:10 -05:00
40d3780319 eaten latch 2024-11-10 03:54:50 -05:00
f696952488 feed cell viability tuple 2024-11-10 03:54:34 -05:00
bddb770ef6 eat tail 2024-11-10 03:37:53 -05:00
f6e3792cb1 step from head to tail_fill zero *food path finding 2024-11-10 03:37:17 -05:00
857e32e5fe GetShuffledIndices function, to remove direction search bias 2024-11-10 03:35:36 -05:00
7 changed files with 181 additions and 89 deletions

View file

@ -8,11 +8,15 @@ 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;
}; };
@ -21,23 +25,26 @@ 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;
float3 normal = normalize(mul(input.norm, (float3x3)sk_inst[id].world)); // flip input normals to treat backfaces like normal
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 {
return input.color; 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);
} }

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="11" android:versionCode="17"
android:versionName="1.15" android:versionName="1.27"
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,23 +49,32 @@ 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
start outside of the box (about 6-9 segments long) my tempo
snake is moving like normal but is parented to an offset pose that keeps their head centered on l_con tap (a) btn to step, and repeatedly to set tempo
so you can easily get a feel for how the snake moves once a tempo has been set you can stop tapping and you'll coast at that tempo
(sort of like writhing around in zero g) if you tap again it resets the tempo latch, so if you just tapped once it's an easy way to pause
*dreamlike* and if you keep tapping you can control your pace and find a new tempo
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
mount/unmount box for orbital_view || handheld || desk/space view this makes it easier to play alongside a video, as your attention span can drift in and out
if placed within 15 degree cone of head view then and you can take tougher parts slower
mount (zero out relative x offset)
bug(s) bug(s)
food.spawn fails to find a spot and defaults to zero ...
``` ```

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using StereoKit; using StereoKit;
namespace snake; namespace snake;
@ -9,6 +10,7 @@ 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");
@ -29,8 +31,13 @@ static class Arts
} }
} }
mat_backbox.Transparency = Transparency.Add;
mat_backbox.FaceCull = Cull.Front; mat_backbox.FaceCull = Cull.Front;
mat_backbox.Transparency = Transparency.Add;
mat_backbox.DepthTest = DepthTest.LessOrEq;
mat_backbox.DepthWrite = false;
mat_box.Chain = mat_backbox;
} }
public static void Frame() public static void Frame()
@ -59,13 +66,16 @@ 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(mat_backbox, Matrix.Identity); meshes["InsideOut"].Draw(
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) if (Mono.in_cone.state && Mono.box_mode == Mono.BoxMode.Hold || Mono.box_mode == Mono.BoxMode.Mount)
{ {
meshes["Hanging"].Draw( meshes["Hanging"].Draw(
mat_unlit, mat_unlit,
@ -78,14 +88,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)
) )
); );
@ -93,7 +103,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.snake[0] + Mono.snake_dir) == Mono.food; bool food_next = !Mono.eaten_latch.state && (Mono.snake[0] + Mono.snake_dir) == Mono.food;
if (!food_next) if (!food_next)
{ {
meshes["Tongue"].Draw( meshes["Tongue"].Draw(
@ -149,8 +159,24 @@ static class Arts
// holes // holes
foreach (KeyValuePair<XYZi, XYZi> hole in Mono.holes) foreach (KeyValuePair<XYZi, XYZi> hole in Mono.holes)
{ {
meshes["Hole"].Draw( Vec3 hole_normal = V.XYZ(
mat_unlit, 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,
Matrix.TRS( Matrix.TRS(
hole.Key.ToVec3, hole.Key.ToVec3,
Quat.LookDir(hole.Value.ToVec3), Quat.LookDir(hole.Value.ToVec3),
@ -169,13 +195,18 @@ static class Arts
10 * Time.Stepf 10 * Time.Stepf
); );
} }
meshes["Food"].Draw( if (!Mono.eaten_latch.state)
mat_mono, {
Matrix.TR( float food_t = Mono.eaten_latch.delta == -1 ? Maths.smooth_stop((float)Mono.step_t) : 1;
Mono.food.ToVec3, meshes["Food"].Draw(
food_ori mat_mono,
) 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 = 2, SD_Y = 1, SD_Z = 2; public const int SD_X = 3, SD_Y = 2, SD_Z = 3;
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,7 +27,8 @@ 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); public static XYZi food = new(2, 0, 0); // [!] start random to keep new game fresh?
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
@ -63,8 +64,11 @@ 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;
float ssx = Maths.smooth_start(sx); Renderer.CameraRoot *= Matrix.R(
box_pose.orientation *= Quat.FromAngles(0, sx * 180.0f * Time.Stepf, 0); 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
@ -102,7 +106,13 @@ static class Mono
public static void Step() public static void Step()
{ {
if (s_array[snake[0] + snake_dir] > -1) // 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)
{ {
// lose condition // lose condition
bool stuck = true; bool stuck = true;
@ -120,14 +130,8 @@ static class Mono
return; return;
} }
XYZi next_pos = snake[0] + snake_dir; bool in_or_around_box = s_array.InRange(snake[0] + snake_dir);
XYZi inset_pos = new( if (!in_or_around_box)
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;
} }
@ -157,7 +161,7 @@ static class Mono
snake[0] += snake_dir; snake[0] += snake_dir;
} }
in_box.Step(s_array.InRange(snake[0])); in_box.Step(box_space.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);
@ -175,51 +179,62 @@ static class Mono
} }
// eat // eat
if (food == snake[0]) if (!eaten_latch.state)
{ {
eat_timestamp = Time.Total; eaten_latch.Step(food == snake[0]);
grow_buffer += 3; if (eaten_latch.delta == +1)
{
eat_timestamp = Time.Total;
grow_buffer += 3;
Feed(); SFX.crisp_nom.PlayBox(snake[0]);
SFX.crisp_nom.PlayBox(snake[0]); }
} else {
(bool viable, XYZi cell) = Feed();
eaten_latch.Step(!viable);
if (eaten_latch.delta == -1)
{
food = cell;
}
} }
} }
static void Feed() static (bool, XYZi) Feed()
{ {
head_fill.Clear(-1); // [!] handle out of the box exception on tail
Gas(head_fill, snake[0]); // by making the spatial arrays encapsulate the layer outside of the box
// 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]);
XYZi farthest_cell = new XYZi(0, 0, 0); // step from head using directions towards the tail
int farthest_dist = 0; // and stop at either 1 cell away from the tail or 5 spaces away from the head
// [*] design configurable (we basically want it to be as far away as it can reasonably be) bool viable = false;
const int max_dist = 6; XYZi cell = snake[0];
// [!] given how it loops over the space it directionally biases the results for (int step = 0; step < 5; step++)
for (int sx = -s_array.Xslen; s_array.InX(sx); sx++)
{ {
for (int sy = -s_array.Yslen; s_array.InY(sy); sy++) int min_dist = 100;
XYZi min_cell = new();
int[] dir_indices = GetShuffledIndices(directions);
for (int i = 0; i < directions.Length; i++)
{ {
for (int sz = -s_array.Zslen; s_array.InZ(sz); sz++) 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))
{ {
XYZi sv = new XYZi(sx, sy, sz); min_dist = tail_dist;
int dist = head_fill[sv]; min_cell = dir_cell;
bool good_dist = dist > farthest_dist && dist < max_dist; viable = true;
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;
// }
} }
food = farthest_cell; return (viable, cell);
} }
// space fill algorithm // space fill algorithm
@ -248,6 +263,23 @@ 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[]
{ {
@ -269,4 +301,14 @@ 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,14 +39,17 @@ 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;
Input.HandVisible(Handed.Max, !con_tracked); // bool hand_tracked = Input.HandSource(Handed.Right) > HandSource.None;
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;