Compare commits
30 commits
352535eaa2
...
6d495d8307
Author | SHA1 | Date | |
---|---|---|---|
6d495d8307 | |||
56829852dc | |||
5f49d94910 | |||
470fa38107 | |||
2eb15dd992 | |||
a906d2bfdc | |||
cfbd489c22 | |||
17f38b83bd | |||
9aeafa7ba0 | |||
ffc9e3fecb | |||
4a7f536999 | |||
5b96fa3df3 | |||
a81cc0073a | |||
5b0a096502 | |||
c1f0bcc956 | |||
3fdea357fc | |||
6121d333cf | |||
11ac4207e9 | |||
196ba2955a | |||
7dc2cfe6a4 | |||
b6e20580e6 | |||
2900070ea9 | |||
9b5a17cb0c | |||
c87d56ef43 | |||
350e4f734b | |||
40d3780319 | |||
f696952488 | |||
bddb770ef6 | |||
f6e3792cb1 | |||
857e32e5fe |
7 changed files with 181 additions and 89 deletions
|
@ -8,11 +8,15 @@ 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;
|
||||
};
|
||||
|
||||
|
@ -21,23 +25,26 @@ 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;
|
||||
|
||||
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));
|
||||
|
||||
|
||||
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);
|
||||
o.color = color * sk_inst[id].color;
|
||||
|
||||
return o;
|
||||
}
|
||||
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)
BIN
Assets/meshes/assets.glb
(Stored with Git LFS)
Binary file not shown.
|
@ -2,8 +2,8 @@
|
|||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.dofdev.snake"
|
||||
android:versionCode="11"
|
||||
android:versionName="1.15"
|
||||
android:versionCode="17"
|
||||
android:versionName="1.27"
|
||||
android:installLocation="auto"
|
||||
>
|
||||
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="32" />
|
||||
|
|
29
readme.md
29
readme.md
|
@ -49,23 +49,32 @@ 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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
bug(s)
|
||||
food.spawn fails to find a spot and defaults to zero
|
||||
...
|
||||
|
||||
```
|
63
src/Arts.cs
63
src/Arts.cs
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using StereoKit;
|
||||
|
||||
namespace snake;
|
||||
|
@ -9,6 +10,7 @@ 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");
|
||||
|
||||
|
@ -29,8 +31,13 @@ static class Arts
|
|||
}
|
||||
}
|
||||
|
||||
mat_backbox.Transparency = Transparency.Add;
|
||||
|
||||
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()
|
||||
|
@ -59,13 +66,16 @@ static class Arts
|
|||
|
||||
// box
|
||||
Hierarchy.Push(Mono.box_pose.ToMatrix(Mono.box_scale));
|
||||
meshes["InsideOut"].Draw(mat_unlit, Matrix.Identity);
|
||||
meshes["InsideOut"].Draw(mat_backbox, Matrix.Identity);
|
||||
// meshes["InsideOut"].Draw(mat_unlit, Matrix.Identity);
|
||||
meshes["InsideOut"].Draw(
|
||||
mat_box,
|
||||
Matrix.Identity
|
||||
);
|
||||
meshes["Corrugation"].Draw(
|
||||
Mono.in_dist.state ? mat_justcolor : mat_unlit,
|
||||
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(
|
||||
mat_unlit,
|
||||
|
@ -78,14 +88,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)
|
||||
)
|
||||
);
|
||||
|
@ -93,7 +103,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.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)
|
||||
{
|
||||
meshes["Tongue"].Draw(
|
||||
|
@ -149,8 +159,24 @@ static class Arts
|
|||
// holes
|
||||
foreach (KeyValuePair<XYZi, XYZi> hole in Mono.holes)
|
||||
{
|
||||
meshes["Hole"].Draw(
|
||||
mat_unlit,
|
||||
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,
|
||||
Matrix.TRS(
|
||||
hole.Key.ToVec3,
|
||||
Quat.LookDir(hole.Value.ToVec3),
|
||||
|
@ -169,13 +195,18 @@ static class Arts
|
|||
10 * Time.Stepf
|
||||
);
|
||||
}
|
||||
meshes["Food"].Draw(
|
||||
mat_mono,
|
||||
Matrix.TR(
|
||||
Mono.food.ToVec3,
|
||||
food_ori
|
||||
)
|
||||
);
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Hierarchy.Pop();
|
||||
|
||||
|
|
130
src/Mono.cs
130
src/Mono.cs
|
@ -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 = 2, SD_Y = 1, SD_Z = 2;
|
||||
public const int SD_X = 3, SD_Y = 2, SD_Z = 3;
|
||||
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,7 +27,8 @@ 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);
|
||||
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 enum BoxMode
|
||||
|
@ -63,8 +64,11 @@ static class Mono
|
|||
if (Input.Key(Key.MouseCenter).IsActive())
|
||||
{
|
||||
float sx = Input.Mouse.posChange.x;
|
||||
float ssx = Maths.smooth_start(sx);
|
||||
box_pose.orientation *= Quat.FromAngles(0, sx * 180.0f * Time.Stepf, 0);
|
||||
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);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -102,7 +106,13 @@ static class Mono
|
|||
|
||||
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
|
||||
bool stuck = true;
|
||||
|
@ -120,14 +130,8 @@ static class Mono
|
|||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
bool in_or_around_box = s_array.InRange(snake[0] + snake_dir);
|
||||
if (!in_or_around_box)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -157,7 +161,7 @@ static class Mono
|
|||
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
|
||||
{
|
||||
holes.Add(snake[0], snake_dir);
|
||||
|
@ -175,51 +179,62 @@ static class Mono
|
|||
}
|
||||
|
||||
// eat
|
||||
if (food == snake[0])
|
||||
if (!eaten_latch.state)
|
||||
{
|
||||
eat_timestamp = Time.Total;
|
||||
grow_buffer += 3;
|
||||
eaten_latch.Step(food == snake[0]);
|
||||
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);
|
||||
Gas(head_fill, snake[0]);
|
||||
|
||||
// handle out of the box exception on tail
|
||||
// [!] handle out of the box exception on tail
|
||||
// by making the spatial arrays encapsulate the layer outside of the box
|
||||
tail_fill.Clear(-1);
|
||||
Gas(tail_fill, snake[snake_len - 1]);
|
||||
|
||||
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++)
|
||||
// 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++)
|
||||
{
|
||||
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);
|
||||
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;
|
||||
}
|
||||
min_dist = tail_dist;
|
||||
min_cell = dir_cell;
|
||||
viable = true;
|
||||
}
|
||||
}
|
||||
cell = min_cell;
|
||||
// if (min_dist <= 1)
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
|
||||
food = farthest_cell;
|
||||
return (viable, cell);
|
||||
}
|
||||
|
||||
// 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
|
||||
static readonly XYZi[] directions = new XYZi[]
|
||||
{
|
||||
|
@ -269,4 +301,14 @@ 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,14 +39,17 @@ 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;
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
|
Loading…
Add table
Reference in a new issue