{"id":790,"date":"2024-09-12T13:00:04","date_gmt":"2024-09-12T13:00:04","guid":{"rendered":"https:\/\/timohonig.nl\/?p=790"},"modified":"2024-10-09T06:54:02","modified_gmt":"2024-10-09T06:54:02","slug":"dual-dynamite","status":"publish","type":"post","link":"https:\/\/timohonig.nl\/index.php\/2024\/09\/12\/dual-dynamite\/","title":{"rendered":"Dual Dynamite"},"content":{"rendered":"\n<p><strong>About<\/strong><br>Dual Dynamite is a top-down, single-player game set in a post-apocalyptic world. Players must scavenge a bunker for ammunition while fending off other scavengers. The game offers a variety of weapons to choose from, which players can use to gather as many points as possible.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Dual Dynamite Gameplay Video\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/S9pqtmt8Ylo?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p class=\"has-text-align-center\"><strong>Project Info<\/strong><br>Group Size: 4<br>Project Length: 8 Weeks<br>Project Date: May-June 2023<br>Engine &amp; Tools: Unity, Visual Studio Code &amp; Trello<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"has-text-align-center\"><strong>Summary<\/strong><br><a href=\"#WorldGeneration\" data-type=\"internal\" data-id=\"#WorldGeneration\">World Generation<br><\/a><a href=\"#PlayerController\" data-type=\"internal\" data-id=\"#PlayerController\">Player Controller &amp; Hud<\/a><br><a href=\"#Props\" data-type=\"internal\" data-id=\"#Props\">Interactive Props &amp; Pickups<\/a><br><a href=\"#Enemies\" data-type=\"internal\" data-id=\"#Enemies\">Enemies<\/a><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p id=\"WorldGeneration\">World Generation<br>For the world generation, we aimed for something infinite, allowing players to run in one direction without ever encountering a world border. This presented a challenge for me. After theory crafting for a while, I decided to use a dictionary with <code>Vector2Int<\/code> as the key and <code>GameObject<\/code> as the value, representing the location of the room and the prefab to spawn. The nine rooms surrounding the player are always rendered, and once the player enters a room, enemies begin to spawn. When a room is no longer rendered, it unloads itself and saves its state, so when the player returns, the room remains unchanged.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"DualDynamite WorldGeneration\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/4k68INxihNw?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>World Generation Code<\/summary>\n<pre class=\"wp-block-code\"><code>public class RoomManager : MonoBehaviour\n{\n    &#91;Header(\"RoomGeneration\")]\n    &#91;SerializeField] private List&lt;GameObject&gt; prefabs;\n    private Dictionary&lt;Vector2Int, GameObject&gt; rooms;\n\n    &#91;Header(\"RoomSettings\")]\n    &#91;SerializeField] private Transform worldRoot;\n    &#91;SerializeField] private Vector2 roomSize;\n    &#91;SerializeField] private Transform player;\n    private Vector2Int playerRoomLocation;\n    private Vector2Int previousPlayerRoomLocation;\n\n    private void Start()\n    {\n        ActivateSurroundingRooms();\n    }\n\n    private void Update()\n    {\n        UpdatePlayerRoom();\n    }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Updates the player's room location and activates the surrounding rooms\n    \/\/\/ &lt;\/summary&gt;\n    private void UpdatePlayerRoom()\n    {\n        Vector2Int currentPlayerRoomLocation = GetPlayerGridCoordinate(player);\n\n        if (currentPlayerRoomLocation != previousPlayerRoomLocation)\n        {\n            ActivateSurroundingRooms();\n            DeactivateNotSurroundingRooms();\n            previousPlayerRoomLocation = currentPlayerRoomLocation;\n        }\n    }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Activates the rooms surrounding the player\n    \/\/\/ &lt;\/summary&gt;\n    private void ActivateSurroundingRooms()\n    {\n        GetPlayerGridCoordinate(player);\n\n        int x = playerRoomLocation.x;\n        int y = playerRoomLocation.y;\n\n        for (int i = -1; i &lt;= 1; i++)\n        {\n            for (int j = -1; j &lt;= 1; j++)\n            {\n                Activate(new Vector2Int(x + i, y + j));\n            }\n        }\n    }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Deactivates the rooms that are not surrounding the player\n    \/\/\/ &lt;\/summary&gt;\n    private void DeactivateNotSurroundingRooms()\n    {\n        \/\/ Create a copy of the keys to avoid modifying the dictionary while iterating\n        List&lt;Vector2Int&gt; roomCoordinates = new List&lt;Vector2Int&gt;(rooms.Keys);\n\n        foreach (Vector2Int coordinate in roomCoordinates)\n        {\n            \/\/ Check if the coordinate is within the desired range around the player\n            if (!IsCoordinateWithinRange(coordinate, playerRoomLocation))\n            {\n                Deactivate(coordinate);\n            }\n        }\n    }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Checks if a coordinate is within the desired range around the player\n    \/\/\/ &lt;\/summary&gt;\n    private bool IsCoordinateWithinRange(Vector2Int coordinate, Vector2Int centerCoordinate)\n    {\n        \/\/ Define the range by adding\/subtracting 1 from the center coordinate\n        int minX = centerCoordinate.x - 1;\n        int maxX = centerCoordinate.x + 1;\n        int minY = centerCoordinate.y - 1;\n        int maxY = centerCoordinate.y + 1;\n\n        \/\/ Check if the coordinate is within the range\n        return coordinate.x &gt;= minX &amp;&amp; coordinate.x &lt;= maxX &amp;&amp; coordinate.y &gt;= minY &amp;&amp; coordinate.y &lt;= maxY;\n    }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Activates a room at the given grid coordinate\n    \/\/\/ &lt;\/summary&gt;\n    public void Activate(Vector2Int coordinate)\n    {\n        \/\/ Create a new dictionary if it doesn't exist\n        if (rooms == null)\n            rooms = new Dictionary&lt;Vector2Int, GameObject&gt;();\n\n        \/\/ Does the coordinate already have a room registered?\n        if (rooms.ContainsKey(coordinate))\n        {\n            rooms&#91;coordinate].SetActive(true);\n            return;\n        }\n\n        \/\/ Create a new room\n        GameObject prefab = prefabs&#91;Random.Range(0, prefabs.Count)];\n        if (prefab == null)\n            throw new System.NullReferenceException(\"&lt;color=#FF8888&gt;There is an empty spot in the prefabs list of the RoomManager&lt;\/color&gt;\");\n\n        GameObject newRoom = Instantiate(prefab, worldRoot);\n        \/\/ Set the name\n        newRoom.transform.name = $\"{prefab.transform.name} {coordinate}\";\n        newRoom.transform.localPosition = new Vector3(roomSize.x * coordinate.x, 0, roomSize.y * coordinate.y);\n        \/\/ Add to the dictionary\n        rooms.Add(coordinate, newRoom);\n    }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Deactivates a room at the given grid coordinate\n    \/\/\/ &lt;\/summary&gt;\n    public void Deactivate(Vector2Int coordinate)\n    {\n        \/\/ Create a new dictionary if it doesn't exist\n        if (rooms == null)\n            rooms = new Dictionary&lt;Vector2Int, GameObject&gt;();\n\n        \/\/ Does the coordinate already have a room registered?\n        if (rooms.ContainsKey(coordinate))\n        {\n            rooms&#91;coordinate].SetActive(false);\n        }\n    }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Clears all rooms and deletes the game objects parented to them\n    \/\/\/ &lt;\/summary&gt;\n    public void Clear()\n    {\n        \/\/ If no dictionary exists, there's nothing to clear\n        if (rooms == null)\n            return;\n\n        \/\/ Go through every entry in the dictionary and destroy the game object\n        foreach (KeyValuePair&lt;Vector2Int, GameObject&gt; kvp in rooms)\n        {\n            Destroy(kvp.Value);\n        }\n        \/\/ Clear the dictionary\n        rooms.Clear();\n    }\n\n    \/\/\/ &lt;summary&gt;\n    \/\/\/ Gets the grid coordinate of the player\n    \/\/\/ &lt;\/summary&gt;\n    public Vector2Int GetPlayerGridCoordinate(Transform playerTransform)\n    {\n        \/\/ Calculate the player's position relative to the worldRoot\n        Vector3 localPosition = playerTransform.position - worldRoot.position;\n\n        \/\/ Calculate the grid coordinates based on the roomSize\n        playerRoomLocation.x = Mathf.RoundToInt(localPosition.x \/ roomSize.x);\n        playerRoomLocation.y = Mathf.RoundToInt(localPosition.z \/ roomSize.y);\n\n        return playerRoomLocation;\n    }\n}<\/code><\/pre>\n<\/details>\n\n\n\n<p id=\"PlayerController\">Player Controller &amp; Hud<br>For the Player Controller, we wanted something simple. I created a class that allows the player to move and dash in all directions. I added extras like animations and particle effects, and I ensured that all important statistics were easily accessible through the hierarchy.<br><br>I decided to create something special for the HUD by loading a spinning 3D object using a RenderCam. While it didn&#8217;t require much code, setting it up in Unity took a bit of work. <br><br>I also worked on things like:<br>&#8211; Highscores<br>&#8211; Keybinds<br>&#8211; Scene Transistions<br>&#8211; Player HP<br>&#8211; Player Combat<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"DualDynamite UI\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/igztpIbtDf4?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>Player Controller &amp; Hud Code<\/summary>\n<pre class=\"wp-block-code\"><code>public class PlayerController : MonoBehaviour\n{\n    &#91;Header(\"PlayerMovement\")]\n    &#91;SerializeField] private float moveSpeed;\n    &#91;SerializeField] private float dashSpeed;\n    Vector3 moveDir;\n\n    &#91;Header(\"DashSettings\")]\n    &#91;SerializeField] private float cooldown;\n    &#91;SerializeField] private bool isDashing = false;\n\n    &#91;SerializeField] private Image dash;\n\n    private float timer;\n    private float dashDuration = 0.4f; \n    private float dashTimer = 0f; \n\n\n    &#91;Header(\"SpeedControl\")]\n    &#91;SerializeField] private float groundDrag;\n    &#91;SerializeField] private float maxVelocity;\n    &#91;SerializeField] private float resetMaxVelocity;\n\n    &#91;Header(\"References\")]\n    &#91;SerializeField] private Transform orientation;\n    &#91;SerializeField] private Transform getForward;\n    &#91;SerializeField] private Animator animator;\n    &#91;SerializeField] private ParticleSystem walkParticle;\n    &#91;SerializeField] private ParticleSystem dashParticle;\n    private Rigidbody rb;\n    private PauseMenu pauseMenu;\n\n    private static PlayerController _instance;\n    public static PlayerController Instance\n    {\n        get\n        {\n            if (_instance == null)\n                _instance = FindObjectOfType&lt;PlayerController&gt;();\n            return _instance;\n        }\n    }\n\n    private void Start()\n    {\n        rb = GetComponent&lt;Rigidbody&gt;();\n        pauseMenu = FindObjectOfType&lt;PauseMenu&gt;();\n\n        dash.enabled = false;\n    }\n    private void Update()\n    {\n        Vector3 currentVelocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z);\n\n        if (currentVelocity.magnitude &gt; maxVelocity)\n        {\n            Vector3 horizontalVelocityDir = currentVelocity.normalized;\n            rb.velocity = horizontalVelocityDir * maxVelocity + Vector3.up * rb.velocity.y;\n        }\n\n        if (Input.GetKeyDown(Keybind.Instance.GetMoveForwardKey()) ||\n            Input.GetKeyDown(Keybind.Instance.GetMoveBackwardKey()) ||\n            Input.GetKeyDown(Keybind.Instance.GetMoveLeftKey()) ||\n            Input.GetKeyDown(Keybind.Instance.GetMoveRightKey()))\n        {\n            animator.SetTrigger(\"isWalking\");\n            animator.ResetTrigger(\"isIdle\");\n            walkParticle.Play();\n        }\n        else if (rb.velocity.magnitude &lt; 0.01f)\n        {\n            animator.SetTrigger(\"isIdle\");\n            animator.ResetTrigger(\"isWalking\");\n            walkParticle.Stop();\n        }\n\n        if (cooldown &lt; timer)\n        {\n            dash.enabled = true;\n        }\n        else\n        {\n            dash.enabled = false;\n        }\n    }\n\n    private void FixedUpdate()\n    {\n        if (pauseMenu.isPaused)\n        {\n            return;\n        }\n\n        float horizontal = 0f;\n        float vertical = 0f;\n\n        if (Input.GetKey(Keybind.Instance.GetMoveLeftKey()))\n        {\n            horizontal -= 1f;\n        }\n        else if (Input.GetKey(Keybind.Instance.GetMoveRightKey()))\n        {\n            horizontal += 1f;\n        }\n        else if (Input.GetKey(Keybind.Instance.GetMoveForwardKey()))\n        {\n            vertical += 1f;\n        }\n        else if(Input.GetKey(Keybind.Instance.GetMoveBackwardKey()))\n        {\n            vertical -= 1f;\n        }\n\n        \/\/ Setting the MoveDirection\n        moveDir = getForward.forward * vertical + getForward.right * horizontal;\n\n        \/\/ Adding the force\n        rb.AddForce(moveDir.normalized * moveSpeed, ForceMode.Impulse);\n\n        rb.drag = groundDrag;\n\n        timer += Time.fixedDeltaTime;\n\n        if (isDashing)\n        {\n            dashTimer += Time.deltaTime;\n\n            if (dashTimer &gt;= dashDuration)\n            {\n                \/\/ Dash duration has been reached, stop dashing\n                isDashing = false;\n                dashTimer = 0f;\n                maxVelocity = resetMaxVelocity;\n                dashParticle.Stop();\n            }\n        }\n        else\n        {\n            if (Input.GetKey(Keybind.Instance.GetDashKey()) &amp;&amp; cooldown &lt; timer)\n            {\n                Dash();\n                timer = 0;\n                dashParticle.Play();\n            }\n        }\n    }\n\n    private void Dash()\n    {\n        isDashing = true;\n        maxVelocity = dashSpeed;\n        rb.AddForce(moveDir.normalized * dashSpeed, ForceMode.Impulse);\n    }\n}<\/code><\/pre>\n<\/details>\n\n\n\n<p id=\"Props\">Interactive Props &amp; Pickups<br>We wanted to hide the pickups in destroyable props, I came up with the idea to give all the props 3 health stages, after breaking the first 2 it will become clear if there is a pickup and which one it is. When breaking the prop the player will be able to pick it up.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Dual Dynamite Props &amp; Pickups\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/5bac9juuClY?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>Interactive Props &amp; Pickups Code<\/summary>\n<pre class=\"wp-block-code\"><code>public class BreakableBox : MonoBehaviour\r\n{\r\n    &#91;Header(\"BoxStages\")]\r\n    &#91;SerializeField] private GameObject boxStage1;\r\n    &#91;SerializeField] private GameObject boxStage2;\r\n    &#91;SerializeField] private GameObject boxStage3;\r\n\r\n    &#91;Header(\"Animations &amp; Sounds\")]\r\n    &#91;SerializeField] private Animation boxHit;\r\n    &#91;SerializeField] private AudioSource boxHitSound;\r\n\r\n    &#91;Header(\"Settings\")]\r\n    &#91;SerializeField] private float health = 80;\r\n    &#91;SerializeField] private float stage1 = 60;\r\n    &#91;SerializeField] private float stage2 = 30;\r\n    &#91;SerializeField] private float stage3 = 0;\r\n   \r\n    private void Start()\r\n    {\r\n        SetBoxStage();\r\n    }\r\n\r\n    private void OnTriggerEnter(Collider other)\r\n    {\r\n        if (other.CompareTag(\"Bullet\"))\r\n        {\r\n            health -= 10;\r\n            boxHit.Play();\r\n            boxHitSound.Play();\r\n            SetBoxStage();\r\n        }\r\n    }\r\n\r\n    \/\/\/ &lt;summary>\r\n    \/\/\/ Sets the box stage when the amount of damage is reached\r\n    \/\/\/ &lt;\/summary>\r\n    private void SetBoxStage()\r\n    {\r\n        if (health > stage1)\r\n        {\r\n            \/\/ Box is in stage 1\r\n            boxStage1.SetActive(true);\r\n            boxStage2.SetActive(false);\r\n            boxStage3.SetActive(false);\r\n        }\r\n        else if (health > stage2)\r\n        {\r\n            \/\/ Box is in stage 2\r\n            boxStage1.SetActive(false);\r\n            boxStage2.SetActive(true);\r\n            boxStage3.SetActive(false);\r\n        }\r\n        else if (health > stage3)\r\n        {\r\n            \/\/ Box is in stage 3\r\n            boxStage1.SetActive(false);\r\n            boxStage2.SetActive(false);\r\n            boxStage3.SetActive(true);\r\n        }\r\n        else\r\n        {\r\n            \/\/ Box is destroyed\r\n            ScoreManager.Instance.GainScore(1);\r\n\r\n            this.gameObject.SetActive(false);\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<\/details>\n\n\n\n<p id=\"Enemies\">Enemies<br>When we initially started this project i did not plan on making the enemies, but 7 weeks in we had reached a roadblock. The code for the enemies stopped worked so in the last week of the project i picked up the task of making enemies. I did not have time to figure out how behaviour trees work so i decided on hard coding my enemies. I made 2 scripts for the AI, one for walking, and one for attacking. The enemies walk a straight path towards the player and when they reach their attack range, they attack.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Dual Dynamite Enemies\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/7f12q5UzPU4?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>Enemies Code<\/summary>\n<pre class=\"wp-block-code\"><code>public class EnemyAI : MonoBehaviour\r\n{\r\n    private NavMeshAgent agent;\r\n    private float timeSinceDestinationUpdate;\r\n    private GameObject player;\r\n    private float originalSpeed;\r\n\r\n    &#91;SerializeField] private ParticleSystem walkParticle;\r\n\r\n    private void Start()\r\n    {\r\n        agent = GetComponent&lt;NavMeshAgent>();\r\n        player = PlayerController.Instance.gameObject;\r\n        originalSpeed = agent.speed;\r\n\r\n        CheckIfOnNavMesh();\r\n\r\n        timeSinceDestinationUpdate = 0f;\r\n    }\r\n\r\n    private void Update()\r\n    {\r\n        timeSinceDestinationUpdate += Time.deltaTime;\r\r\n        \r\n        if (player != null &amp;&amp; timeSinceDestinationUpdate > 0.25f)\r\n        {\r\n            agent.SetDestination(player.transform.position);\r\n            timeSinceDestinationUpdate = 0;\r\n        }\r\n\r\n        if (agent != null)\r\n        {\r\n            if (agent.velocity.magnitude > 0f &amp;&amp; !agent.isStopped)\r\n            {\r\n                \/\/ Play the walk particle\r\n                if (!walkParticle.isPlaying)\r\n                {\r\n                    walkParticle.Play();\r\n                }\r\n            }\r\n            else\r\n            {\r\n                \/\/ Stop or don't play the particle\r\n                if (walkParticle.isPlaying)\r\n                {\r\n                    walkParticle.Stop();\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    private void CheckIfOnNavMesh()\r\n    {   \r\n        NavMeshHit hit;\r\n        if (!NavMesh.SamplePosition(transform.position, out hit, 0.1f, NavMesh.AllAreas))\r\n        {\r\n            Debug.LogWarning(\"Enemy is not on the navmesh. Adjust the position.\");\r\n        }\r\n    }<\/code><\/pre>\n<\/details>\n","protected":false},"excerpt":{"rendered":"<p>Dual Dynamite is a top-down, single-player game set in a post-apocalyptic world. Players must scavenge a bunker for ammunition while fending off other scavengers. The game offers a variety of weapons to choose from, which players can use to gather as many points as possible.<\/p>\n","protected":false},"author":1,"featured_media":792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9,8],"tags":[],"class_list":["post-790","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-all-projects","category-favorite"],"_links":{"self":[{"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/posts\/790","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/comments?post=790"}],"version-history":[{"count":26,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/posts\/790\/revisions"}],"predecessor-version":[{"id":825,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/posts\/790\/revisions\/825"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/media\/792"}],"wp:attachment":[{"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/media?parent=790"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/categories?post=790"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/timohonig.nl\/index.php\/wp-json\/wp\/v2\/tags?post=790"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}