Adding trick spawn and movement to Experiment scene

This commit is contained in:
hz
2026-01-31 18:35:14 -06:00
parent 0c031b558e
commit c24846d568
7 changed files with 298 additions and 75 deletions
+37
View File
@@ -0,0 +1,37 @@
# GGJ2026 Agent Notes
These notes are for coding agents working in this repo. Keep them short, actionable, and up to date.
## Project Overview
- Unity project (2D). Main code in `Assets/Scripts/`.
- Input uses Unity Input System with `Assets/InputSystem_Actions.inputactions`.
- Scene of interest: `Assets/Scenes/Level1.unity`.
## Input/Controllers
- Player controllers are `PlayerPlatformerController` and `PlayerPickupController`.
- Each controller instantiates its own `InputActionAsset` and can filter to a specific device.
- Device filtering is based on:
- `controlSchemeName == "Gamepad"` and/or
- `gamepadIndex >= 0`.
- When no matching gamepad is available, controllers ignore input until the device appears.
- Gamepad index inference:
- `actionMapName = Player` -> index 0
- `actionMapName = Player2/Player3/...` -> index (N-1)
- If you add/rename action maps, update inference or set `gamepadIndex` explicitly.
## Level1 Scene Notes
- Player 2/3/4 are placed directly in the scene and set to Gamepad.
- Current expected indices:
- Player2 -> `gamepadIndex: 0`
- Player3 -> `gamepadIndex: 1`
- Player4 -> `gamepadIndex: 2`
- If you want a 4th controller player, set `gamepadIndex: 3` and ensure `controlSchemeName: Gamepad`.
## Prefabs
- `Assets/Prefabs/Player.prefab` defaults to `controlSchemeName: Keyboard&Mouse` and `gamepadIndex: -1`.
- If you instantiate players at runtime, set `gamepadIndex` and `controlSchemeName` per player.
## Editing Tips
- Use `rg` for searches (`rg -n "term" Assets`).
- Prefer `apply_patch` for small edits.
- Avoid touching `Library/`, `Temp/`, and `Logs/`.
+58 -66
View File
@@ -1,6 +1,6 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &4063493560390081740
--- !u!1 &700000000
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -8,12 +8,11 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4813753125575581197}
- component: {fileID: 417219162817149911}
- component: {fileID: 3710467127740942334}
- component: {fileID: 6722296303968501955}
- component: {fileID: 493370774516464490}
- component: {fileID: 8358140610337579774}
- component: {fileID: 700000001}
- component: {fileID: 700000002}
- component: {fileID: 700000003}
- component: {fileID: 700000004}
- component: {fileID: 700000005}
m_Layer: 0
m_Name: Trick
m_TagString: Untagged
@@ -21,29 +20,29 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4813753125575581197
--- !u!4 &700000001
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4063493560390081740}
m_GameObject: {fileID: 700000000}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -2.684, y: 1.03, z: 0}
m_LocalScale: {x: 0.411, y: 0.4437, z: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &417219162817149911
--- !u!212 &700000002
SpriteRenderer:
serializedVersion: 2
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4063493560390081740}
m_GameObject: {fileID: 700000000}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
@@ -83,49 +82,52 @@ SpriteRenderer:
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_SortingOrder: 2
m_MaskInteraction: 0
m_Sprite: {fileID: -9095717837082945937, guid: 207ee8102dd4143d288186ef0be518ee, type: 3}
m_Color: {r: 0.735849, g: 0, b: 0, a: 1}
m_Sprite: {fileID: 8755586062601758735, guid: 8530a46c93e30d54cb57c68ae0a1b06e, type: 3}
m_Color: {r: 0.9, g: 0.35, b: 0.9, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 1, y: 2}
m_Size: {x: 1, y: 1}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_SpriteSortPoint: 0
--- !u!114 &3710467127740942334
MonoBehaviour:
--- !u!50 &700000003
Rigidbody2D:
serializedVersion: 5
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4063493560390081740}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 246e7020310b45d4d92f3af76f126c12, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TrickController
--- !u!114 &6722296303968501955
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4063493560390081740}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 246e7020310b45d4d92f3af76f126c12, type: 3}
m_Name:
m_EditorClassIdentifier: '::'
--- !u!61 &493370774516464490
m_GameObject: {fileID: 700000000}
m_BodyType: 0
m_Simulated: 1
m_UseFullKinematicContacts: 0
m_UseAutoMass: 0
m_Mass: 1
m_LinearDamping: 0
m_AngularDamping: 0.05
m_GravityScale: 0
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_Interpolate: 0
m_SleepingMode: 1
m_CollisionDetection: 0
m_Constraints: 4
--- !u!61 &700000004
BoxCollider2D:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4063493560390081740}
m_GameObject: {fileID: 700000000}
m_Enabled: 1
serializedVersion: 3
m_Density: 1
@@ -153,42 +155,32 @@ BoxCollider2D:
m_UsedByEffector: 0
m_CompositeOperation: 0
m_CompositeOrder: 0
m_Offset: {x: 0, y: 0}
m_Offset: {x: -0.0029773563, y: -0.000000029802322}
m_SpriteTilingProperty:
border: {x: 0, y: 0, z: 0, w: 0}
pivot: {x: 0.5, y: 0.5}
oldSize: {x: 1, y: 2}
newSize: {x: 1, y: 2}
oldSize: {x: 0.15, y: 0.31}
newSize: {x: 1, y: 1}
adaptiveTilingThreshold: 0.5
drawMode: 0
adaptiveTiling: 0
m_AutoTiling: 0
m_Size: {x: 1, y: 2}
m_Size: {x: 0.16040364, y: 0.31522292}
m_EdgeRadius: 0
--- !u!50 &8358140610337579774
Rigidbody2D:
serializedVersion: 5
--- !u!114 &700000005
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4063493560390081740}
m_BodyType: 0
m_Simulated: 1
m_UseFullKinematicContacts: 0
m_UseAutoMass: 0
m_Mass: 1
m_LinearDamping: 0
m_AngularDamping: 0.05
m_GravityScale: 1
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_Interpolate: 0
m_SleepingMode: 1
m_CollisionDetection: 0
m_Constraints: 0
m_GameObject: {fileID: 700000000}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a8cb5c6bd0dd4b8281aec250714b6fc1, type: 3}
m_Name:
m_EditorClassIdentifier:
treatTag: Interactable
moveSpeed: 3.5
targetRefreshInterval: 0.4
consumeDistance: 0.35
wanderInterval: 1.2
+1 -1
View File
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2c0004e9e01ba3648b75049ead682f94
guid: 8bf8957665594a718535d06aba0cdee7
PrefabImporter:
externalObjects: {}
userData:
+48
View File
@@ -2576,6 +2576,53 @@ PrefabInstance:
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 99cce68fc82caea44b5a140697be00a8, type: 3}
--- !u!1 &2100000000
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2100000001}
- component: {fileID: 2100000002}
m_Layer: 0
m_Name: NPC Spawner
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2100000001
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2100000000}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &2100000002
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2100000000}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 14b0bf07aa1abc44a8ebd00cecacd6f4, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Spawner
objectToSpawn: {fileID: 700000000, guid: 8bf8957665594a718535d06aba0cdee7, type: 3}
spawnInterval: 6
maxAlive: 1
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
@@ -2604,3 +2651,4 @@ SceneRoots:
- {fileID: 792686806}
- {fileID: 1685410085}
- {fileID: 375061916}
- {fileID: 2100000001}
+29 -1
View File
@@ -1,4 +1,5 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spawner : MonoBehaviour
@@ -7,6 +8,9 @@ public class Spawner : MonoBehaviour
[SerializeField] private GameObject objectToSpawn;
// Time interval between spawns
public float spawnInterval = 1f;
[SerializeField] private int maxAlive = 0;
private readonly List<GameObject> spawned = new List<GameObject>();
void Start()
{
@@ -21,8 +25,32 @@ public class Spawner : MonoBehaviour
{
// Wait for the specified interval
yield return new WaitForSeconds(spawnInterval);
if (objectToSpawn == null)
{
continue;
}
if (maxAlive > 0 && CountAlive() >= maxAlive)
{
continue;
}
// Spawn the object at the spawner's position and rotation
Instantiate(objectToSpawn, transform.position, Quaternion.identity);
var instance = Instantiate(objectToSpawn, transform.position, Quaternion.identity);
spawned.Add(instance);
}
}
private int CountAlive()
{
for (var i = spawned.Count - 1; i >= 0; i--)
{
if (spawned[i] == null)
{
spawned.RemoveAt(i);
}
}
return spawned.Count;
}
}
+115 -6
View File
@@ -1,16 +1,125 @@
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Collider2D))]
public class TrickController : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
[SerializeField] private string treatTag = "Interactable";
[SerializeField] private float moveSpeed = 3.5f;
[SerializeField] private float targetRefreshInterval = 0.4f;
[SerializeField] private float consumeDistance = 0.35f;
[SerializeField] private float wanderInterval = 1.2f;
private Rigidbody2D body;
private Transform target;
private float nextTargetTime;
private Vector2 wanderDirection;
private float nextWanderTime;
private void Awake()
{
body = GetComponent<Rigidbody2D>();
body.bodyType = RigidbodyType2D.Dynamic;
body.gravityScale = 1f;
body.freezeRotation = true;
body.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
body.interpolation = RigidbodyInterpolation2D.Interpolate;
}
// Update is called once per frame
void Update()
private void Update()
{
if (Time.time >= nextTargetTime)
{
target = FindNearestTreat();
nextTargetTime = Time.time + targetRefreshInterval;
}
if (target == null && Time.time >= nextWanderTime)
{
wanderDirection = Random.insideUnitCircle.normalized;
if (wanderDirection.sqrMagnitude < 0.01f)
{
wanderDirection = Vector2.right;
}
nextWanderTime = Time.time + wanderInterval;
}
TryConsumeTreat();
}
private void FixedUpdate()
{
Vector2 desiredVelocity;
var stopForTreat = false;
if (target != null)
{
var direction = ((Vector2)target.position - (Vector2)transform.position).normalized;
desiredVelocity = new Vector2(direction.x * moveSpeed, 0f);
stopForTreat = Vector2.Distance(transform.position, target.position) <= consumeDistance;
}
else
{
desiredVelocity = new Vector2(wanderDirection.x * (moveSpeed * 0.6f), 0f);
}
var velocity = body.linearVelocity;
velocity.x = stopForTreat ? 0f : desiredVelocity.x;
body.linearVelocity = velocity;
}
private Transform FindNearestTreat()
{
var treats = GameObject.FindGameObjectsWithTag(treatTag);
if (treats == null || treats.Length == 0)
{
return null;
}
Transform best = null;
var bestDistance = float.MaxValue;
var origin = transform.position;
for (var i = 0; i < treats.Length; i++)
{
var candidate = treats[i];
if (!candidate.activeInHierarchy)
{
continue;
}
if (!candidate.TryGetComponent<Collider2D>(out var collider) || !collider.enabled)
{
continue;
}
var distance = Vector2.Distance(origin, candidate.transform.position);
if (distance < bestDistance)
{
bestDistance = distance;
best = candidate.transform;
}
}
return best;
}
private void TryConsumeTreat()
{
var hits = Physics2D.OverlapCircleAll(transform.position, consumeDistance);
for (var i = 0; i < hits.Length; i++)
{
var hit = hits[i];
if (hit == null || !hit.CompareTag(treatTag))
{
continue;
}
Destroy(hit.gameObject);
if (target == hit.transform)
{
target = null;
}
break;
}
}
}
+10 -1
View File
@@ -1,2 +1,11 @@
fileFormatVersion: 2
guid: 246e7020310b45d4d92f3af76f126c12
guid: a8cb5c6bd0dd4b8281aec250714b6fc1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: