NNY Unity

Gode unity-resources

Simple game (easy)
2D gamemaking (medium)
Sebastian Lague (hard)

Tank-spil

Tank projekt som det var d. 21/11: NNY Tank NOV21
Tank projekt med nedenstående funktionalitet: NNY Tank COMPLETE

Reloading

Full script:  TankShooting

Vi ændrer vores TankShooting-script til at inkludere en Reload-funktion, der gør at vi er nød til at reloade, istedet for bare at skyde for evigt.

Først laver vi nogle nye variabler:

Ammo: Hvor meget ammo, der er loaded og kan bruges lige nu.
TotalAmmo: Hvor meget ammo, vi har i alt.
MaxClipSize: Hvor meget ammo vi max kan have loaded.

Herefter laver vi en reload funktion:

Først finder vi ud af hvor meget vi skal reloade, ved at subtraktere vores maxClipSize med den ammo vi har lige nu.

F.eks. 30 – 17 = 13: Der skal reloades 13 skud.

int ammoToReload = maxClipSize – ammo;

Vi bruger dette tal (13), til at se om vi overhovedet har nok totalAmmo tilbage (fx: if totalAmmo < 13), og hvis vi ikke har, så addere vi det til ammo og sætter totalAmmo til 0. Altså har vi loaded vores sidste ammunition, og har ikke mere tilbage.

if(totalAmmo<ammoToReload)
{
    ammo+=totalAmmo;
    totalAmmo=0;
}
Ellers hvis vi har nok ammo, så sætter vi bare vores ammo til max og subtrakterer vores total med den mængde skud der skulle reloades (ammoToReload).
else
{
    ammo=maxClipSize;
    totalAmmo-=ammoToReload;
}
Til sidst opdatere vi teksten ved at kalde vores AdjustAmmo, som desuden skal ændres, så den passer med vores nye variabler. Vi justere nu totalAmmo istedet for ammo.
public void AdjustAmmo(int adj)
{
    totalAmmo+=adj;
    ammoText.text=”AMMO: “+ammo+” / “+totalAmmo;
}
Vi skal også kunne kalde Reload-funktionen ved knaptryk:
if(Input.GetKeyDown(KeyCode.R))
{
    Reload();
}
OBS: Husk også at ændre Scene-reload knappen i ScoreScript, hvis du satte den til ‘R’.

Camera obstruction fix

Full script: CameraScript

Der var nogle klager over at man tit endte i situationer, hvor kameraet kigger direkte ind i en væg, så man ikke kan se fjenderne.

Her laver vi et script, som tjekker om der er en væg mellem spilleren og kameraet, og hvis der er så gør vi den gennemsigtig.

OBS: Dette script er ikke ideelt, da den kun gør en væg gennemsigtig af gangen uanset størrelsen, men det virker godt til simple spil som vores :).

Unity setup

Først skal vi ændre lidt på tingene i Unity. Sæt det material, der bruges på væggene til “Transparent”.

Herefter skal vi lave et nyt Layer, ved at vælge et GameObject og trykke > Layer > AddLayer > skriv nyt layer navn (fx: “Wall”).

Vælg derefter alle de gameobjecter, der spærrer kameraet og sæt deres later til det nye layer, du lavede.

Nu er vi klar til at programmere i CameraScript. Først laver vi en LayerMask, som indeholder nogle layers.

    public LayerMask wallMask;

Denne sættes i Unity til de layers vi gerne vil tjekke efter kamera-obstruktioner på. I dette tilfælde vores nye Layer; “Wall”:

code

Vi har bruge for at vide om der er noget mellem to punkter, hvilket der heldigvis er en god funktion til. Vi sætter følgende in i vores Update funktion:

        RaycastHit hit;
        Physics.Linecast(transform.position, player.position, out hit, wallMask);
        Debug.DrawLine(transform.position, player.position);

Her har vi først en Hit-variable, som indeholder var end der bliver ramt mellem spilleren og kameraet.

Så bruger vi Physics.Linecast, til at kigge mellem vores (kameraets) position og spillerens position. Den har også en out hit, hvilket betyder den smider resultatet ud til vores hit-variabel, og vores wallMask fra tidligere, som gør at Linecast ignorerer alle lag, der ikke er deri.

Til sidst har vi en Debug.DrawLine, der tegner den streg, som Linecast laver. Dette gør det lettere for os at visualisere, hvad koden gør.

 

Variablen hit indeholder alt information om hvad der er blevet ramt i vores Linecast. Når vi skriver hit.transform, får vi fat i Transform-componenten på det objekt der er ramt, hvis der er et.
Da der er en mulighed for at vi ikke ramte noget, så tjekker vi først om denne Transform overhovedet findes ved at spørge om den er null(ingen ting) eller ej.
        if(hit.transform != null)
        {
            Material mat = hit.transform.GetComponent<MeshRenderer>().materials[0];
        }
Hvis den findes, så får vi fat i dens material fra dens MeshRenderer. Man kan visualisere dette ved at kigge på en væg i unity og finde den’s material efter samme formular:
Få MeshRenderer > Gå ned i Materials liste > Vælg 0 (første material).
Nu har vi væggens material i koden, så vi kan ændre farven. Farve-værdier i Unity består af 4 værdier mellem 0 og 1. RGBA: Røg, Grøn, Blå, Alpha (gennemsigtighed).
Da vi gerne vil gøre væggen gennemsigtig, skal vi ændre på A (alpha), så vi giver materialet en ny farve, hvor alpha er ændret.
Man kan ikke ændre alpha på materialet direkte, så vi laver istedet en ny farve som er lig med materialiets farve.
Den’s alpha kan vi så ændre og derefter sætte materialets farve lig denne nye farve igen.
Lidt kompliceret, men slut resultatet er at vores farve er den samme, men med en anden alpha.
            Color c = mat.color;
            c.a = 0.5f;
            mat.color = c;
Hvis man spiller spillet nu, så kan man se at væggene bliver gennemsigtigt, men de forbliver også gennemsigtige selv når væggen ikke længere spærre for kameraet.
Altså må vi huske hvilken væg der er gennemsigtig og gøre den normal igen, når den ikke længere er i vejen.
Først laver vi en variable i det yderste scope (hvad er scope?), som så:
Denne variable bruger vi til at gemme den ramte væg i vores if-statement, som ser sådan her ud nu:
        if(hit.transform != null)
        {
            Material mat = hit.transform.GetComponent<MeshRenderer>().materials[0];
            Color c = mat.color;
            c.a = 0.5f;
            mat.color = c;
            lastWall = hit.transform;
        }
Ovenover det if-statement laver vi et andet, som tjekker om lastWall findes og om den er lig med hit.transform:
if (lastWall != null && lastWall != hit.transform)
{
}
Heri skal væggen’s alpha sættes tilbage til 1, så den ikke er gennemsigtig.
I if-statementet, tjekker vi om lastWall findes, da vi ikke kan sætte farven på et objekt der ikke findes. Derudover må lastWall heller ikke være lig med hit.transform.
Inde i if-statementet skal vi som sagt sætte farven igen, hvilket er næsten det samme som før:
            Material mat = lastWall.GetComponent<MeshRenderer>().materials[0];
            Color c = mat.color;
            c.a = 1f;
            mat.color = c;
Hvis man spiller spillet nu, så virker alting som forventet. Dog er det ikke god programmering at skrive det samme flere gange på denne måde, så for at gøre koden “pæn”, så kan vi sætte farve-skift koden ned i sin egen funktion.
Making code reusable
Men koden er ikke helt den samme, tænker du måske. Det er rigtigt og vi kan finde disse forskelle og fodre dem til funktionen via parametre. Kig på funktionen og find forskellene. Den første er hvem vi får materialet fra:
lastWall.GetComponent…
hit.transform.GetComp…
De er dog begge en transform altså kan vi give funktionen en transform. Den næste forskel er alpha:
c.a = 0.5f;
c.a = 1f;
Værdien vi angiver her er en float (decimal værdi), så i vores funktion angiver vi 2 parametre; en transform og en float:
    void SetAlphaOnObj(Transform trans, float alpha)
    {
    }
Nu kan vi indsætte farve-skift koden vi lavede tidligere, men erstatte lastWallhit.transform med vores trans-parameter og float-værdien med vores alpha-parameter:
    void SetAlphaOnObj(Transform trans, float alpha)
    {
        Material mat = trans.GetComponent<MeshRenderer>().materials[0];
        Color c = mat.color;
        c.a = alpha;
        mat.color = c;
    }
Så kan vi erstatte farve-skift koden i if-statementsne til vores funktion:
        if (lastWall != null && lastWall != hit.transform)
        {
            SetAlphaOnObj(lastWall, 1f);
        }
        if(hit.transform != null)
        {
            SetAlphaOnObj(hit.transform, 0.5f);
            lastWall = hit.transform;
        }

Fixing scorescript

Full script: ScoreScript, HighscoreSystem

I denne sektion fixer vi vores ScoreScript, man kan ændre på om et level er færdigt når en timer når 0 eller når man får en vis mængde points.

Derudover, så gemmes scoren ikke mellem levels, så ens points starter forfra i nye levels.

Keeping score

Vi starter med at gemme scoren. Da vi kommer til at gemme scoren ofte, må vi først sikre os at den starter på 0, så man ikke lige pludselig starter med 1000 points i første level. Dette gør vi ved at sætte scoren til 0, efter vi er færdig med at bruge den i Highscore-scriptet. Det gør vi ved at placere følgende linje til sidst i start-funktionen:

 PlayerPrefs.SetInt(“Score”, 0);

Vi sætter også følgende ind i Start-funktionen i ScoreScript:

score = PlayerPrefs.GetInt(“Score”);

Denne linje sætter scoren til den score vi har gemt globalt, så hvis vi får 100 points i første level, så starter vi også med 100 points i anden level.

Derudover fixer vi en lille fejl, der blev lavet i GameOver, hvor vi brugte sceneCount istedet for sceneCountInBuildSettings. Vores GameOver-funktion, skulle gerne se sådan her ud:

    public void GameOver()
    {
        PlayerPrefs.SetInt(“Score”, score);
        SceneManager.LoadScene(SceneManager.sceneCountInBuildSettings-1);
    }
Man kan dog stadig snyde i dette system ved at få en masse point > slukke spillet før GameOver-scenen > starte det igen.
Dette fixer vi ved at bruge OnApplicationQuit i ScoreScript, som kaldes, når spillet lukkes.
    void OnApplicationQuit()
    {
        PlayerPrefs.SetInt(“Score”, 0);
    }
gameover by timer or by points

Først laver vi en bool, der holder styr på om banen færdiggøres ved timer der udløber eller points der når en vis mængde. Derudover laver vi en variable, der siger hvor mange points man skal have for at “vinde” banen:

    public bool gameOverByTimer = false;

    public int scoreToGameOver = 10;

    int scoreThisLevel = 0;

I Update, ændrer vi vores timer-if-statement til også at checke vores bool:

if(timer <= 0 && gameOverByTimer)
Efter if-statementet spørger vi så om vores score er nået scoreToGameOver, og hvis den er gemmer vi scoren og loader næste level:
        else if (scoreThisLevel > scoreToGameOver && !gameOverByTimer)
        {
PlayerPrefs.SetInt(“Score”, score);
            SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex+1);
        }
Gemme score og loade level koden er den samme som under timer, så vi kan ligesom med kamera scriptet genbruge koden i en funktion:
    public void LoadNextLevel()
    {
        // Saves score
        PlayerPrefs.SetInt(“Score”, score);
        // Loads next scene
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex+1);
    }
Vi kan også genbruge vores timerText til at vise hvor langt vi er med pointsne (X ud af Y):
        if (gameOverByTimer)
            timerText.text = “Timer: ” + timer.ToString(“F0”);
        else
            timerText.text = “Score: ” + scoreThisLevel + ” out of ” + scoreToGameOver;

Grunden til at vi bruger scoreThisLevel er at hvis vi brugte score og man kom fra et level med 10 points til et level hvor man scoreToGameOver er 10, så ville banen bare blive skippet, da man allerede har opnået målet. ScoreThisLevel sikre at man får 10 point i det level vi er i.

Vi skal derfor også huske at opdatere den i AdjustScore:

    public void AdjustScore(int adj)
    {
        // Add the adjustment to total score / score this lvl
        score += adj;
        scoreThisLevel += adj;
        // Update score text
        scoreText.text = “Score: ” + score;
    }

Done!

Spillet kan nu klare at i laver mange forskellige levels (bare husk og adde dem til build settings) og derudover kan i udvide med jeres egne kreationer, som power-ups, flere slags våben osv.

Det var mega awesome at være jeres lærer :). Kontakt mig bare, hvis i har problemer med jeres projekter:

Twitter
Mail
Facebook

Next Post

Previous Post

Leave a Reply

© 2019

Theme by Anders Norén