diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6ac8ae5 --- /dev/null +++ b/AGENTS.md @@ -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/`. diff --git a/Assets/Prefabs/Trick.prefab b/Assets/Prefabs/Trick.prefab index 3c9c001..a58d44f 100644 --- a/Assets/Prefabs/Trick.prefab +++ b/Assets/Prefabs/Trick.prefab @@ -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 diff --git a/Assets/Prefabs/Trick.prefab.meta b/Assets/Prefabs/Trick.prefab.meta index 3e5b61b..29ee505 100644 --- a/Assets/Prefabs/Trick.prefab.meta +++ b/Assets/Prefabs/Trick.prefab.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 2c0004e9e01ba3648b75049ead682f94 +guid: 8bf8957665594a718535d06aba0cdee7 PrefabImporter: externalObjects: {} userData: diff --git a/Assets/Scenes/Experiment.unity b/Assets/Scenes/Experiment.unity index f442550..a99ce47 100644 --- a/Assets/Scenes/Experiment.unity +++ b/Assets/Scenes/Experiment.unity @@ -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} diff --git a/Assets/Scripts/Spawner.cs b/Assets/Scripts/Spawner.cs index 710aacc..5a501ae 100644 --- a/Assets/Scripts/Spawner.cs +++ b/Assets/Scripts/Spawner.cs @@ -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 spawned = new List(); 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; + } } diff --git a/Assets/Scripts/TrickController.cs b/Assets/Scripts/TrickController.cs index 6d6f839..a25d4b0 100644 --- a/Assets/Scripts/TrickController.cs +++ b/Assets/Scripts/TrickController.cs @@ -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(); + 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(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; + } } } diff --git a/Assets/Scripts/TrickController.cs.meta b/Assets/Scripts/TrickController.cs.meta index e6af027..c2db15a 100644 --- a/Assets/Scripts/TrickController.cs.meta +++ b/Assets/Scripts/TrickController.cs.meta @@ -1,2 +1,11 @@ fileFormatVersion: 2 -guid: 246e7020310b45d4d92f3af76f126c12 \ No newline at end of file +guid: a8cb5c6bd0dd4b8281aec250714b6fc1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: