Back to portfolio
Unity / Photon / Multiplayer

Urban Cleanup Crew Dokumentation

En elevvänlig genomgång av spelets viktigaste system: Photon multiplayer, Photon Voice, player movement, inventory, cleaning mechanics, spawns, monster-AI och hur delarna hänger ihop i Unity.

Unity version 2022.3.47f1
PUN multiplayer
Voice Photon Voice
Co-op städspel

Vad spelet är

Urban Cleanup Crew är ett multiplayer-spel i Unity där flera spelare kan gå med i samma room, spawnas in i en bana, plocka upp verktyg, rengöra fläckar och hantera objekt tillsammans. Projektet kombinerar klassiska spelprogrammeringssystem med nätverk.

Gameplay från Urban Cleanup Crew
Gameplay-bild från projektet som visar hur spelet ser ut i praktiken.
01 Main Menu

Spelare hostar eller joinar med party-kod.

02 Photon Room

Alla spelare hamnar i samma nätverksrum.

03 Spawn

Photon skapar player-prefabs i scenen.

04 Gameplay

Spelare rör sig, plockar upp items och städar.

05 Sync

RPC, ownership och PhotonView håller klienter uppdaterade.

Det viktigaste för nya spelprogrammerare

Ett spelprojekt blir lätt svårt om man försöker förstå allt samtidigt. Tänk istället i system: input, movement, camera, UI, inventory, multiplayer, AI och game rules.

GameObject + Component

I Unity är nästan allt ett GameObject med komponenter. Ett script är en komponent som ger objektet beteende.

Update vs FixedUpdate

Update() passar input och kamera. FixedUpdate() passar physics och Rigidbody-rörelse.

Prefab

En prefab är en återanvändbar mall. Photon kan instansiera prefabs över nätverket.

Raycast

Raycast används för att se vad spelaren tittar på, till exempel dörrar, pickup-items och stains.

Layers och Tags

Layers hjälper raycasts och physics att filtrera objekt. Tags används för exempelvis Player.

State

Booleans som InventoryOpen och HandsAreBusy styr vad spelaren får göra just nu.

Vad Photon är och hur det fungerar

Photon är en multiplayer-tjänst för Unity. I det här projektet används Photon PUN. PUN gör att spelare kan ansluta till samma server, skapa rooms, skicka RPC-anrop, instansiera nätverksobjekt och synka viktiga data.

Förklaring av hur Photon kopplar ihop spelare, rooms och server
Visuell förklaring av hur Photon fungerar som mellanlager mellan spelare, rooms och nätverkade objekt.

Master Server

Första steget är att koppla upp spelet mot Photon. När anslutningen är klar kan spelet gå vidare till lobby/room.

PhotonNetwork.ConnectUsingSettings();

Room

Ett room är matchen/lobbyn där spelarna möts. I projektet skapas privata rum med party-kod.

PhotonNetwork.CreateRoom(code, options);
PhotonNetwork.JoinRoom(code);

De viktigaste Photon-begreppen

PhotonView
Komponenten som gör ett objekt identifierbart över nätverket. Behövs för ownership, RPC och sync.
IsMine
Berättar om objektet ägs av den lokala spelaren. Input ska nästan alltid bara köras om photonView.IsMine.
MasterClient
En spelare som får extra ansvar, till exempel att spawna monster, trash och stains.
RPC
Remote Procedure Call. Ett sätt att säga: kör den här metoden på andra klienter också.
Ownership
Bestämmer vilken klient som får styra ett nätverksobjekt, till exempel ett föremål som plockas upp.

Grundregel: låt varje spelare styra sin egen input lokalt, men använd Photon för att visa resultatet för andra spelare. Därför ser man ofta if (!photonView.IsMine) return; i player-scripts.

Hur voice chat görs med Photon Voice

Photon Voice är ett tillägg till Photon som hanterar mikrofonljud mellan spelare. I projektet används PhotonVoiceView tillsammans med ett eget script: VoiceChatController.

På lokal spelare

Den lokala spelarens mikrofon får sända ljud. Voice detection kan aktiveras så att tystnad inte skickas.

voiceView.RecorderInUse.TransmitEnabled = true;
voiceView.RecorderInUse.VoiceDetection = true;

På remote players

Andra spelares objekt ska inte försöka sända från din mikrofon. Därför stängs transmit av på remote objects.

if (!photonView.IsMine)
    voiceView.RecorderInUse.TransmitEnabled = false;

För att voice chat ska fungera behöver player-prefaben normalt ha Photon-komponenter, Photon Voice-komponenter och rätt koppling mellan Recorder/Speaker. Scriptet ser till att bara rätt spelare använder mikrofonen.

Player controller

Spelaren styrs av en Rigidbody FPS-controller. Det betyder att movement går genom Unitys physics-system, medan kameran och input läses i vanliga Update().

Movement

RigidbodyFPSController hanterar walk, run, jump, acceleration, air control och landing dip.

Camera

Musen styr yaw på spelarens rotation och pitch på kameran. När inventory öppnas begränsas kameran.

if (!pv.IsMine) return;

moveInput = new Vector3(
    Input.GetAxisRaw("Horizontal"),
    0,
    Input.GetAxisRaw("Vertical")
).normalized;

Det viktiga här är att remote players inte får läsa din input. De ska bara visas korrekt genom nätverkssync.

Inventory, suitcase och equipment

Spelet har två nivåer av föremålshantering: först läggs objekt i suitcase, sedan kan de flyttas till hotbar/equipment.

01 Raycast

Spelaren tittar på ett föremål.

02 Pickup

E lägger objektet i suitcase.

03 Suitcase

Objektet parentas till en ledig slot.

04 Equip

Objektet flyttas till hand slot.

05 Use

Cleaning-systemet kollar om item är aktivt.

SuitcaseToggle

Öppnar/stänger suitcase med Tab och sätter globala state-flaggor som stoppar movement/interactions.

PlayerEquipmentManager

Hantera hotbar slots, UI-icons, aktiva items och animatorn HoldingItem.

Cleaning-systemet

Det mest tekniskt intressanta systemet är StainNetwork. Det rengör inte bara ett objekt direkt, utan målar bort alpha från stain-texturen runtime. Det gör att fläcken faktiskt försvinner gradvis där spelaren rengör.

Hur en stain rengörs

  • Spelaren håller musknappen.
  • En raycast träffar stain.
  • Scriptet kontrollerar att spelaren håller ett aktivt cleaning item.
  • Brushen tar bort alpha från texturen.
  • När tillräckligt mycket alpha är borta räknas stain som clean.

Varför det är bra att lära sig

Det visar hur gameplay kan byggas från flera små system: raycast, material, texture pixels, UI feedback, nätverk och object spawning.

float progress = 1f - (totalAlphaNow / (float)totalAlphaStart);

if (progress >= eraseFractionToClean && !isCleaned)
    photonView.RPC(nameof(RPC_CleanStain), RpcTarget.AllBuffered, painterActorNr);

När stain blir clean spawnas en trash bag och stain tas bort. I multiplayer görs detta via Photon så alla spelare ser samma resultat.

Procedural level generation

SimpleLevelGenerator bygger en bana genom att koppla ihop prefabs via Doorway-punkter. Det är ett tydligt exempel på procedural generation i mindre skala.

Doorways

Varje room/hallway har markers som visar var nya bitar kan kopplas på.

Prefab-val

Generatorn väljer rooms, hallways, unique rooms, elevator eller dead ends.

Overlap check

Physics overlap används för att undvika att nya rum placeras inuti gamla.

Detta är viktigt för elever eftersom det visar att banor inte alltid behöver byggas helt manuellt. Man kan skapa regler och låta koden bygga variation.

Monster och AI

Monster-systemet använder Unity NavMesh. MasterClient kör AI-logiken och skickar animation states till de andra klienterna.

Detection

Monstret hittar närmaste objekt med taggen Player och kontrollerar avståndet.

States

Om spelaren är nära går monstret mot spelaren. Om spelaren är väldigt nära spelas bite-animation.

if (!PhotonNetwork.IsMasterClient) return;

Transform targetPlayer = FindClosestPlayer();
agent.SetDestination(targetPlayer.position);

Det här är en bra multiplayer-princip: låt en auktoritativ klient bestämma AI, annars kan flera datorer ge olika beslut.

Viktigaste scriptsen

MainMenuScript
Photon-anslutning, host/join, party-kod, lobby UI och start av spelet.
GameSceneManager
Spawnar spelare på rätt spawn point när gameplay-scenen laddas.
MovementScript
Rigidbody FPS movement, camera look, jump, sprint, landing effect och inventory camera lock.
PlayerItemPickup
Raycast, pickup UI, outline och flytt av föremål mellan world, suitcase och equipment.
StainNetwork
Runtime texture cleaning, progress, RPC-stämplar och trash bag spawn.
LevelGenerator
Procedural rooms/hallways via Doorway-markers och overlap checks.
MonsterFollow
NavMesh-monster som jagar närmaste spelare och synkar animationer.
VoiceChatController
Aktiverar mikrofon på lokal spelare och stänger av sändning på remote players.

Tips till elever som ska börja med spelprogrammering

Börja singleplayer

Bygg movement, camera, pickup och UI lokalt först. Lägg på multiplayer när systemet fungerar.

Synka bara det viktiga

Skicka inte allt över nätverket. Synka position, ägare, animation states och viktiga events.

Använd tydliga prefabs

Player, stains, trash och monster bör vara prefabs med rätt komponenter från början.

Dokumentera states

Multiplayerbuggar blir mycket lättare att felsöka om man vet vem som äger objektet och vilket state det har.

Den viktigaste multiplayer-frågan är alltid: vem bestämmer? I det här projektet bestämmer lokal spelare över sin input, owner bestämmer över sina objekt, och MasterClient bestämmer över globala saker som monster och spawns.