Indholdsfortegnelse:
- Trin 1: Start et nyt enhedsprojekt
- Trin 2: Konfigurer scenen
- Trin 3: Lad os lave nogle partikler
- Trin 4: Sænker partiklerne
- Trin 5: Oprettelse af portalen
- Trin 6: Particle Shaders
- Trin 7: Opret Skybox
- Trin 8: Portallogik
- Trin 9: Næsten færdig
- Trin 10: Sæt appen på din telefon
Video: AR Portal to the Upside Down From Stranger Things: 10 trin (med billeder)
2024 Forfatter: John Day | [email protected]. Sidst ændret: 2024-01-30 08:29
Denne instruktør vil gå gennem oprettelsen af en augmented reality -mobilapp til iPhone med en portal, der fører til hovedet fra Stranger Things. Du kan gå inde i portalen, gå rundt og komme ud igen. Alt inde i portalen kan kun ses gennem portalen, indtil du går indenfor. Når det er inde, vil alt gengives overalt, indtil du går tilbage til den virkelige verden. Vi vil bruge Unity 3D -videospilmotoren med Apple ARKit -plugin. Al den software, vi vil bruge, kan downloades og bruges gratis. Du behøver ikke at være ekspert for at følge med, vi gennemgår hvert trin!
Trin 1: Start et nyt enhedsprojekt
Først skal du downloade Unity3D og sørge for at installere build -filerne til IOS -platformen. Du skal også downloade Xcode og tilmelde dig en gratis Apple -udviklerkonto. Din iPhone skal også køre IOS 11 eller nyere. Fra i dag den 5. februar 2018 er IOS 11.3 ude, men xCode 9.2 har endnu ikke supportfiler til det. Så hvis du kører den allernyeste IOS -version, skal du sørge for at downloade den nyeste Xcode -betaversion fra Apple. Developer.com.
Når du har alle de nødvendige programmer, skal du åbne Unity og starte et nyt projekt, kalde det hvad du vil. Vi får brug for Apple ARKit -plugin'et, så vi kan bruge vores telefons kamera til at registrere jorden og placere objekter på gulvet. Lad os importere det nu ved at gå til fanen Asset Store og søge "ARKit". Du bliver nødt til at oprette en gratis Unity -konto, hvis du ikke allerede har en, og klik derefter på import for at hente pluginet.
Naviger til mappen eksempler i ARKit -mappen, og find "UnityARKitScene." Dobbeltklik på den for at åbne den. Vi kommer til at bruge denne scene som udgangspunkt og bygge videre herfra. Denne scene giver dig som standard mulighed for at registrere jorden, og når du trykker på skærmen, placeres en terning i den position.
Lad os først få vores byggeindstillinger kvadreret væk, så vi ikke glemmer at gøre det senere. Klik på fil, opbyg indstillinger og fjern alle scener fra listen. Klik på tilføj åbne scener for at tilføje vores nuværende. Den sidste ting, vi skal konfigurere her, er i afspillerindstillinger gå ned til bundt -id, og formatet for denne streng er com. YourCompanyName. YourAppName, så i mit tilfælde gør jeg noget som com. MatthewHallberg. PortalTest.
Trin 2: Konfigurer scenen
Tag først et kig til venstre og find spilobjektet kaldet "GeneratePlanes". Med det markeret, skal du se til højre nu og klikke på afkrydsningsfeltet for at deaktivere det. På denne måde har vi ikke de grimme blå firkanter, der genereres, når ARKit registrerer et jordplan. Slet derefter "RandomCube" -spilobjektet, fordi vi ikke vil se det i vores scene.
Nu skal vi først oprette vores portaldør. Slet terningen, der er et barn af "HitCubeParent". Højreklik og vælg Opret tomt spilobjekt. Omdøb den til "Portal". Højreklik nu på dette objekt og opret en terning, dette vil gøre det til et barn af portalen. Omdøb det til "PostLeft", og dette vil være venstre stolpe på vores portal. Skala den, så x er 1 y er 28 og z er en. Gør det samme for det rigtige indlæg. Opret nu den øverste stolpe og skala y til 14. Drej denne sidelæns og flyt den, så den forbinder de andre stolper. Lav hele portalskalaen 1,3 x 1,4 x 1.
Gå til google, og skriv træ eller barktekstur. Download et af disse billeder, og træk det ind i din aktivmappe i Unity. Træk nu billedet til alle dine portalindlæg.
Klik på "Portal" -objektet igen, og klik på tilføj komponent til højre. Tilføj scriptet "UnityARHitTestExample" til det. Der er en tom plads der til "Hit Transform", træk "HitCubeParent" -objektet ind i den slot.
Trin 3: Lad os lave nogle partikler
Nu skal vi bruge Unity Particle -systemet til at lave en røg- og flydende partikeleffekt til inde i vores portal. Gå til Aktiver i den øverste menulinje, standardaktiver og importpartikelsystemer.
Opret to tomme spilobjekter inde i din portal, og kald den ene "SmokeParticles" og den anden "FloatingParticles".
Tilføj en partikelsystemkomponent til røgpartiklerne.
Denne komponent har en masse muligheder, men vi behøver kun at ændre et par.
Skift startfarven til noget mørkeblåt med omkring 50% gennemsigtighed. Lav emissionshastigheden 100. Indvendig form, lav radius.01. I renderer -delen i bunden ændres min størrelse til.8 og max -størrelse til 5. På materialekomponenten skal du bare vælge røgmaterialet fra listen, men vi kommer til at ændre dette senere.
Tilføj et partikelsystem til spilobjektet med flydende partikler nu, og sæt emissionen til 500. Indstil startlevetid til 2, radius til 10, min partikelstørrelse til.01 og maks. Partikelstørrelse til.015. Indstil materialet til standardpartikel for nu.
Tag endelig begge spilobjekter og drej dem 90 grader på x'en og løft dem op i luften, så de udsender ned på portaldøren.
Trin 4: Sænker partiklerne
Da vi vil have disse partikler til at dække et stort område, men også bevæge sig langsomt, er vi nødt til at oprette vores egen prøvefunktion. Så højreklik i aktivmappen, og opret et nyt C# script og kald det "ParticleSample." Kopier og indsæt denne kode:
ved hjælp af System. Collections;
ved hjælp af System. Collections. Generic; ved hjælp af UnityEngine; offentlig klasse ParticleSample: MonoBehaviour {private ParticleSystem ps; // Brug dette til initialisering void Start () {ps = GetComponent (); StartCoroutine (SampleParticleRoutine ()); } IEnumerator SampleParticleRoutine () {var main = ps.main; main.simulationSpeed = 1000f; ps. Play (); yield return new WaitForSeconds (.1f); main.simulationSpeed =.05f; }}
Træk nu dette script ind på hvert af dine partikelsystem -spilobjekter.
Trin 5: Oprettelse af portalen
Nu skal vi oprette portalen, så højreklik på portalspilobjektet og opret en quad. Skala quad, så den dækker hele portalen, dette bliver vores portalvindue. Det første, vi skal tilføje til det, er portalshader, dette vil kun gengive objekter med en anden specifik shader på. Højreklik i mappen Aktiver, og opret en ny lyser uden lys. Fjern alt derinde og indsæt denne kode:
Shader "Portal/portalWindow"
{SubShader {Zwrite off Colormask 0 cull off Stencil {Ref 1 Pass erstat} Pass {}}}
Højreklik i hierarkiet og opret et nyt materiale, kald det PortalWindowMat, find portalsektionen i rullemenuen for dette materiale, og vælg portalvindue. Træk dette materiale ind på din portal quad.
Trin 6: Particle Shaders
Højreklik i mappen aktiver igen, og opret en ny shader. Vi skal lave shaders til partiklerne, der går inde i portalen. Udskift hele koden med dette:
Shader "Portal/Partikler" {
Egenskaber {_TintColor ("Tint Color", Color) = (0.5, 0.5, 0.5, 0.5) _MainTex ("Particle Texture", 2D) = "white" {} _InvFade ("Soft Particles Factor", Range (0.01, 3.0)) = 1.0 _Stencil ("stencil", int) = 6} Kategori {Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane"} Blend SrcAlpha OneMinusSrcAlpha ColorMask RGB Cull Off Lighting Off ZWrite Off SubShader {Stencil {Ref 1 Comp [_Stencil]} Pass {CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #pragma multi_compile_particles #pragma multi_compile_fog #include "UnityCG.cginc" fixed4 _TintColor; struct appdata_t {float4 toppunkt: POSITION; fixed4 farve: FARVE; float2 texcoord: TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID}; struct v2f {float4 toppunkt: SV_POSITION; fixed4 farve: FARVE; float2 texcoord: TEXCOORD0; UNITY_FOG_COORDS (1) #ifdef SOFTPARTICLES_ON float4 projPos: TEXCOORD2; #endif UNITY_VERTEX_OUTPUT_STEREO}; float4 _MainTex_ST; v2f vert (appdata_t v) {v2f o; UNITY_SETUP_INSTANCE_ID (v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO (o); o.vertex = UnityObjectToClipPos (v.vertex); #ifdef SOFTPARTICLES_ON o.projPos = ComputeScreenPos (o.vertex); COMPUTE_EYEDEPTH (o.projPos.z); #endif o.farve = v.farve * _TintColor; o.texcoord = TRANSFORM_TEX (v.texcoord, _MainTex); UNITY_TRANSFER_FOG (o, o.vertex); vende tilbage o; } UNITY_DECLARE_DEPTH_TEXTURE (_CameraDepthTexture); float _InvFade; fixed4 frag (v2f i): SV_Target {#ifdef SOFTPARTICLES_ON float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ (_CameraDepthTexture, UNITY_PROJ_COORD (i.projPos))); float partZ = i.projPos.z; float fade = saturate (_InvFade * (sceneZ-partZ)); i.color.a *= fade; #endif fixed4 col = 2.0f * i.color * tex2D (_MainTex, i.texcoord); UNITY_APPLY_FOG (i.fogCoord, col); return col; } ENDCG}}}}
Opret to nye materialer, et kaldet portalSmoke og et kaldet portalParticles.
For hver enkelt vælg denne shader, fra rullemenuen, i portaler, partikler. For røgpartiklerne vælges en røgtekstur og for partiklerne vælges partikeltekstur. Skift røgenes farve til en mørkere blå med omkring 50% gennemsigtighed. Gå til renderer -komponenten i hvert partikelsystem i din portal og vælg deres respektive materialer, som vi lige har oprettet.
Trin 7: Opret Skybox
For nu virkelig at skabe den omvendte type look, skal vi farvelægge alt mørkeblåt. Til dette vil vi bruge en gennemsigtig skybox, så lav en ny shader og indsæt denne kode:
Shader "Portal/portalSkybox" {
Egenskaber {_Tint ("Tint Color", Color) = (.5,.5,.5,.5) [Gamma] _Exposure ("Exposure", Range (0, 8)) = 1.0 _Rotation ("Rotation", Range (0, 360)) = 0 [NoScaleOffset] _Tex ("Cubemap (HDR)", Cube) = "grå" {} _Stencil ("StencilNum", int) = 6} SubShader {Tags {"Queue" = "Background" "RenderType" = "Baggrund" "PreviewType" = "Skybox"} Sluk ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Stencil {Ref 1 Comp [_Stencil]} Pass {CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #include "UnityCG.cginc "samplerCUBE _Tex; half4 _Tex_HDR; half4 _Tint; halv _Eksponering; float _Rotation; float3 RotateAroundYInDegrees (float3 toppunkt, float grader) {float alfa = grader * UNITY_PI / 180.0; flyde sina, cosa; sincos (alfa, sina, cosa); float2x2 m = float2x2 (cosa, -sina, sina, cosa); return float3 (mul (m, vertex.xz), vertex.y).xzy; } struct appdata_t {float4 toppunkt: POSITION; UNITY_VERTEX_INPUT_INSTANCE_ID}; struct v2f {float4 toppunkt: SV_POSITION; float3 texcoord: TEXCOORD0; UNITY_VERTEX_OUTPUT_STEREO}; v2f vert (appdata_t v) {v2f o; UNITY_SETUP_INSTANCE_ID (v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO (o); float3 rotated = RotateAroundYInDegrees (v.vertex, _Rotation); o.vertex = UnityObjectToClipPos (roteret); o.texcoord = v.vertex.xyz; vende tilbage o; } fixed4 frag (v2f i): SV_Target {half4 tex = texCUBE (_Tex, i.texcoord); half3 c = DecodeHDR (tex, _Tex_HDR); c = c * _Tint.rgb * unity_ColorSpaceDouble.rgb; c *= _Eksponering; returner halv4 (c,.5); } ENDCG}} Fallback Off}
Opret nu et nyt skybox -materiale, kald det "PortalSkybox" og vælg denne portalSkybox -skygge fra portalmenuen. Gå til Vindue, belysning øverst og vælg denne skybox, vi lige har oprettet. Gå til hovedkameraet og sæt klare flag til skybox. Mens vi er her, kan vi tilføje nogle komponenter på vores kamera, så vi kan registrere kollisioner. Føj en stiv kropskomponent til kameraet, og fjern markeringen ved brug af tyngdekraften. Tilføj en kassekollider, og tjek, om den er udløsende. Lav kassekolliderne størrelse, 5 x 1 x 4. Indstil klippeplanet på kameraet til.01.
Trin 8: Portallogik
Det sidste, vi skal gøre, er at oprette den logik, der styrer vores portal. Opret et nyt C# script og kald det PortalController.
ved hjælp af System. Collections;
ved hjælp af System. Collections. Generic; ved hjælp af UnityEngine; navnerum UnityEngine. XR.iOS {public class PortalController: MonoBehaviour {public Material materials; offentlig MeshRenderer meshRenderer; offentlig UnityARVideo UnityARVideo; private bool isInside = false; private bool isOutside = true; // Brug dette til initialisering void Start () {OutsidePortal (); } ugyldig OnTriggerStay (Collider col) {Vector3 playerPos = Camera.main.transform.position + Camera.main.transform.forward * (Camera.main.nearClipPlane * 4); if (transform. InverseTransformPoint (playerPos).z <= 0) {if (isOutside) {isOutside = false; isInside = true; InsidePortal (); }} else {if (isInside) {isInside = false; isOutside = sand; OutsidePortal (); }}} ugyldig OutsidePortal () {StartCoroutine (DelayChangeMat (3)); } ugyldig InsidePortal () {StartCoroutine (DelayChangeMat (6)); } IEnumerator DelayChangeMat (int stencilNum) {UnityARVideo.shouldRender = false; yield return new WaitForEndOfFrame (); meshRenderer.enabled = false; foreach (Materialemåtte i materialer) {mat. SetInt ("_Stencil", stencilNum); } return return new WaitForEndOfFrame (); meshRenderer.enabled = true; UnityARVideo.shouldRender = true; }}}
Træk dette nye script til dit portalvindue. Dette vil overføre os ind og ud af portalen, når kollideren på vores kamera kolliderer med portalvinduet. Nu i funktionen, der ændrer alle materialer, fortæller vi ARkit -pluginet, at de ikke skal gengive rammen, så gå til hovedkameraet og åbn UnityARVideo -scriptet. Opret en offentlig bool shouldRender øverst og sæt den lig med true. Nede i OnPreRender () -funktionen pakkes alt ind i en if -sætning, hvor alt indeni kun kører, hvis shouldRender er sandt. Hele scriptet skal se sådan ud:
ved hjælp af System;
ved hjælp af System. Runtime. InteropServices; ved hjælp af UnityEngine; ved hjælp af UnityEngine. Rendering; navnerum UnityEngine. XR.iOS {public class UnityARVideo: MonoBehaviour {public Material m_ClearMaterial; [HideInInspector] offentlig bool shouldRender = true; private CommandBuffer m_VideoCommandBuffer; privat Texture2D _videoTextureY; privat Texture2D _videoTextureCbCr; privat Matrix4x4 _displayTransform; privat bool bCommandBufferInitialized; public void Start () {UnityARSessionNativeInterface. ARFrameUpdatedEvent += UpdateFrame; bCommandBufferInitialized = false; } ugid UpdateFrame (UnityARCamera cam) {_displayTransform = ny Matrix4x4 (); _displayTransform. SetColumn (0, cam.displayTransform.column0); _displayTransform. SetColumn (1, cam.displayTransform.column1); _displayTransform. SetColumn (2, cam.displayTransform.column2); _displayTransform. SetColumn (3, cam.displayTransform.column3); } void InitializeCommandBuffer () {m_VideoCommandBuffer = ny CommandBuffer (); m_VideoCommandBuffer. Blit (null, BuiltinRenderTextureType. CurrentActive, m_ClearMaterial); GetComponent (). AddCommandBuffer (CameraEvent. BeforeForwardOpaque, m_VideoCommandBuffer); bCommandBufferInitialized = true; } ugyldig OnDestroy () {GetComponent (). FjernCommandBuffer (CameraEvent. BeforeForwardOpaque, m_VideoCommandBuffer); UnityARSessionNativeInterface. ARFrameUpdatedEvent -= UpdateFrame; bCommandBufferInitialized = false; } #if! UNITY_EDITOR public void OnPreRender () {if (shouldRender) {ARTextureHandles handles = UnityARSessionNativeInterface. GetARSessionNativeInterface (). GetARVideoTextureHandles (); hvis (handles.textureY == System. IntPtr. Zero || handles.textureCbCr == System. IntPtr. Zero) {return; } hvis (! bCommandBufferInitialized) {InitializeCommandBuffer (); } Resolution currentResolution = Screen.currentResolution; // Tekstur Y hvis (_videoTextureY == null) {_videoTextureY = Texture2D. CreateExternalTexture (currentResolution.width, currentResolution.height, TextureFormat. R8, false, false, (System. IntPtr) handles.textureY); _videoTextureY.filterMode = FilterMode. Bilinear; _videoTextureY.wrapMode = TextureWrapMode. Repeat; m_ClearMaterial. SetTexture ("_ textureY", _videoTextureY); } // Texture CbCr if (_videoTextureCbCr == null) {_videoTextureCbCr = Texture2D. CreateExternalTexture (currentResolution.width, currentResolution.height, TextureFormat. RG16, false, false, (System. IntPtr) handles.textureCbC); _videoTextureCbCr.filterMode = FilterMode. Bilinear; _videoTextureCbCr.wrapMode = TextureWrapMode. Repeat; m_ClearMaterial. SetTexture ("_ textureCbCr", _videoTextureCbCr); } _videoTextureY. UpdateExternalTexture (handles.textureY); _videoTextureCbCr. UpdateExternalTexture (handles.textureCbCr); m_ClearMaterial. SetMatrix ("_ DisplayTransform", _displayTransform); }} #else public void SetYTexure (Texture2D YTex) {_videoTextureY = YTex; } offentligt tomrum SetUVTexure (Texture2D UVTex) {_videoTextureCbCr = UVTex; } public void OnPreRender () {if (! bCommandBufferInitialized) {InitializeCommandBuffer (); } m_ClearMaterial. SetTexture ("_ textureY", _videoTextureY); m_ClearMaterial. SetTexture ("_ textureCbCr", _videoTextureCbCr); m_ClearMaterial. SetMatrix ("_ DisplayTransform", _displayTransform); } #Afslut Hvis } }
Trin 9: Næsten færdig
Endelig når vi klikker på skærmen og placerer portalen, vil vi have, at den altid vender mod os. For at gøre dette skal du gå til "UnityARHitTestExample" scriptet på portalen. Udskift alt indeni med dette:
ved hjælp af System;
ved hjælp af System. Collections. Generic; navnerum UnityEngine. XR.iOS {public class UnityARHitTestExample: MonoBehaviour {public Transform m_HitTransform; offentlig float maxRayDistance = 30.0f; public LayerMask collisionLayer = 1 <0) {foreach (var hitResult in hitResults) {Debug. Log ("Got hit!"); m_HitTransform.position = UnityARMatrixOps. GetPosition (hitResult.worldTransform); m_HitTransform.rotation = UnityARMatrixOps. GetRotation (hitResult.worldTransform); Debug. Log (string. Format ("x: {0: 0. ######} y: {1: 0. ######}} z: {2: 0. ###### } ", m_HitTransform.position.x, m_HitTransform.position.y, m_HitTransform.position.z)); Vector3 currAngle = transform.eulerAngles; transform. LookAt (Camera.main.transform); transform.eulerAngles = ny Vector3 (currAngle.x, transform.eulerAngles.y, currAngle.z); vende tilbage sandt; }} returner falsk; } // Opdatering kaldes én gang per ramme ugyldig Opdatering () {#if UNITY_EDITOR // vi vil kun bruge dette script på redaktørsiden, selvom der ikke er noget, der forhindrer det i at fungere på enheden, hvis (Input. GetMouseButtonDown (0)) {Ray ray = Camera.main. ScreenPointToRay (Input.mousePosition); RaycastHit ramte; // vi vil prøve at ramme et af flykollider -spilobjekterne, der blev genereret af pluginet // effektivt ligner at kalde HitTest med ARHitTestResultType. ARHitTestResultTypeExistingPlaneUsingExtent if (Physics. Raycast (ray, out hit, maxRayDistance, collisionLayer)) {// vi får positionen fra kontaktpunktet m_HitTransform.position = hit.point; Debug. Log (string. Format ("x: {0: 0. ######} y: {1: 0. ######}} z: {2: 0. ###### } ", m_HitTransform.position.x, m_HitTransform.position.y, m_HitTransform.position.z)); // og rotationen fra transformationen af flykollideren m_HitTransform.rotation = hit.transform.rotation; }} #else if (Input.touchCount> 0 && m_HitTransform! = null) {var touch = Input. GetTouch (0); hvis (touch.phase == TouchPhase. Began || touch.phase == TouchPhase. Moved) {var screenPosition = Camera.main. ScreenToViewportPoint (touch.position); ARPoint -punkt = nyt ARPoint {x = screenPosition.x, y = screenPosition.y}; // Prioriter reults typer ARHitTestResultType resultTypes = {ARHitTestResultType. ARHitTestResultTypeExistingPlaneUsingExtent, // hvis du ønsker at bruge uendelig planer bruge denne: //ARHitTestResultType. ARHitTestResultTypeExistingPlane, ARHitTestResultType. ARHitTestResultTypeHorizontalPlane, ARHitTestResultType. ARHitTestResultTypeFeaturePoint}; foreach (ARHitTestResultType resultType in resultTypes) {if (HitTestWithResultType (point, resultType)) {return; } } } } #Afslut Hvis } } }
Trin 10: Sæt appen på din telefon
Endelig er vi færdige. Gå til filen, byg indstillinger og klik på build. Åbn Xcode, og vælg den mappe, der blev oprettet fra build'en. Vælg dit udviklingsteam, og læg appen på din telefon! Du vil måske ændre farverne på partikler og skybox for at passe til dine behov. Lad mig vide i kommentarerne, hvis du har spørgsmål og tak fordi du kiggede!
Anbefalede:
Stranger Things programmerbar hættetrøje: 9 trin (med billeder)
Stranger Things Programmerbar hættetrøje: Du behøver måske aldrig nogensinde at bruge tid i en mareridtfuld verden af monstre, men nogle gange vil du bare have en skjorte på, der siger, at du helt KUNDE leve der, hvis du ville. Da en sådan skjorte ikke findes på det åbne marked, besluttede vi at lave vores
Lydreaktive lyspæredisplays + Stranger Things : 8 trin (med billeder)
Lydreaktive lyspæreskærme + Stranger Things …: For flere fotos og projektopdateringer: @capricorn_one
Stranger Things væg i en ramme (skriv dine egne beskeder!): 8 trin (med billeder)
Stranger Things Wall in a Frame (Skriv dine egne meddelelser!): Jeg har ment at gøre dette i flere måneder efter at have set en tutorial med julelys (det så godt ud, men hvad er meningen med ikke at vise nogen beskeder, ikke?). Så jeg har lavet denne Stranger Things Wall for noget tid siden, og det tog mig ret lang tid
Stranger Things LED T-Shirt: 8 trin (med billeder)
Stranger Things LED T-Shirt: Materialer, du skal bruge: 1x Almindelig hvid T-shirt Mat sort stofmaling (Amazon) 26x adresserbare RGB LED'er (Polulu) loddemetal og elektrisk tråd varmekrympeslange (Maplin) 1x Arduino Uno 1x USB batteripakke 1x USB-A-kabel 1x nål & Hvid Threa
Appstyret alfabetbræt Inspireret af Stranger Things: 7 trin (med billeder)
App-kontrolleret alfabetbræt Inspireret af Stranger Things: Alt dette begyndte for et par uger siden, da jeg forsøgte at finde ud af, hvad jeg skulle få min niårige niece til jul. Min bror meddelte mig endelig, at hun er en stor fan af Stranger Things. Jeg vidste med det samme, hvad jeg ville have for hende, noget, der