GPGS 구글 플레이 게임 서비스의 모든것 - 2021.11




GPGSBinder.cs 소스입니다



using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using GooglePlayGames;
using GooglePlayGames.BasicApi;
using GooglePlayGames.BasicApi.SavedGame;
using GooglePlayGames.BasicApi.Events;


public class GPGSBinder
{
    static GPGSBinder inst = new GPGSBinder();
    public static GPGSBinder Inst => inst;



    ISavedGameClient SavedGame => 
        PlayGamesPlatform.Instance.SavedGame;

    IEventsClient Events =>
        PlayGamesPlatform.Instance.Events;



    void Init()
    {
        var config = new PlayGamesClientConfiguration.Builder().EnableSavedGames().Build();
        PlayGamesPlatform.InitializeInstance(config);
        PlayGamesPlatform.DebugLogEnabled = true;
        PlayGamesPlatform.Activate();
    }


    public void Login(Action<bool, UnityEngine.SocialPlatforms.ILocalUser> onLoginSuccess = null)
    {
        Init();
        PlayGamesPlatform.Instance.Authenticate(SignInInteractivity.CanPromptAlways, (success) =>
        {
            onLoginSuccess?.Invoke(success == SignInStatus.Success, Social.localUser);
        });
    }

    public void Logout()
    {
        PlayGamesPlatform.Instance.SignOut();
    }


    public void SaveCloud(string fileName, string saveData, Action<bool> onCloudSaved = null)
    {
        SavedGame.OpenWithAutomaticConflictResolution(fileName, DataSource.ReadCacheOrNetwork,
            ConflictResolutionStrategy.UseLastKnownGood, (status, game) =>
            {
                if (status == SavedGameRequestStatus.Success)
                {
                    var update = new SavedGameMetadataUpdate.Builder().Build();
                    byte[] bytes = System.Text.Encoding.UTF8.GetBytes(saveData);
                    SavedGame.CommitUpdate(game, update, bytes, (status2, game2) =>
                    {
                        onCloudSaved?.Invoke(status2 == SavedGameRequestStatus.Success);
                    });
                }
            });
    }

    public void LoadCloud(string fileName, Action<bool, string> onCloudLoaded = null)
    {
        SavedGame.OpenWithAutomaticConflictResolution(fileName, DataSource.ReadCacheOrNetwork, 
            ConflictResolutionStrategy.UseLastKnownGood, (status, game) => 
            {
                if (status == SavedGameRequestStatus.Success)
                {
                    SavedGame.ReadBinaryData(game, (status2, loadedData) =>
                    {
                        if (status2 == SavedGameRequestStatus.Success)
                        {
                            string data = System.Text.Encoding.UTF8.GetString(loadedData);
                            onCloudLoaded?.Invoke(true, data);
                        }
                        else
                            onCloudLoaded?.Invoke(false, null);
                    });
                }
            });
    }

    public void DeleteCloud(string fileName, Action<bool> onCloudDeleted = null)
    {
        SavedGame.OpenWithAutomaticConflictResolution(fileName,
            DataSource.ReadCacheOrNetwork, ConflictResolutionStrategy.UseLongestPlaytime, (status, game) =>
            {
                if (status == SavedGameRequestStatus.Success)
                {
                    SavedGame.Delete(game);
                    onCloudDeleted?.Invoke(true);
                }
                else 
                    onCloudDeleted?.Invoke(false);
            });
    }


    public void ShowAchievementUI() => 
        Social.ShowAchievementsUI();

public void UnlockAchievement(string gpgsId, Action<bool> onUnlocked = null) => 
        Social.ReportProgress(gpgsId, 100, success => onUnlocked?.Invoke(success));

    public void IncrementAchievement(string gpgsId, int steps, Action<bool> onUnlocked = null) =>
        PlayGamesPlatform.Instance.IncrementAchievement(gpgsId, steps, success => onUnlocked?.Invoke(success));


    public void ShowAllLeaderboardUI() =>
        Social.ShowLeaderboardUI();

    public void ShowTargetLeaderboardUI(string gpgsId) => 
        ((PlayGamesPlatform)Social.Active).ShowLeaderboardUI(gpgsId);

    public void ReportLeaderboard(string gpgsId, long score, Action<bool> onReported = null) =>
        Social.ReportScore(score, gpgsId, success => onReported?.Invoke(success));

public void LoadAllLeaderboardArray(string gpgsId, Action<UnityEngine.SocialPlatforms.IScore[]> onloaded = null) => 
        Social.LoadScores(gpgsId, onloaded);

    public void LoadCustomLeaderboardArray(string gpgsId, int rowCount, LeaderboardStart leaderboardStart, 
        LeaderboardTimeSpan leaderboardTimeSpan, Action<bool, LeaderboardScoreData> onloaded = null)
    {
        PlayGamesPlatform.Instance.LoadScores(gpgsId, leaderboardStart, rowCount, LeaderboardCollection.Public, leaderboardTimeSpan, data =>
        {
            onloaded?.Invoke(data.Status == ResponseStatus.Success, data);
        });
    }


    public void IncrementEvent(string gpgsId, uint steps) 
    {
        Events.IncrementEvent(gpgsId, steps);
    }

    public void LoadEvent(string gpgsId, Action<bool, IEvent> onEventLoaded = null)
    {
        Events.FetchEvent(DataSource.ReadCacheOrNetwork, gpgsId, (status, iEvent) =>
        {
            onEventLoaded?.Invoke(status == ResponseStatus.Success, iEvent);
        });
    }

    public void LoadAllEvent(Action<bool, List<IEvent>> onEventsLoaded = null)
    {
        Events.FetchAllEvents(DataSource.ReadCacheOrNetwork, (status, events) =>
        {
            onEventsLoaded?.Invoke(status == ResponseStatus.Success, events);
        });
    }

}








Test.cs 소스입니다



using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
    string log;


    void OnGUI()
    {
        GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, Vector3.one * 3);


        if (GUILayout.Button("ClearLog"))
            log = "";

        if (GUILayout.Button("Login"))
            GPGSBinder.Inst.Login((success, localUser) =>
            log = $"{success}, {localUser.userName}, {localUser.id}, {localUser.state}, {localUser.underage}");

        if (GUILayout.Button("Logout"))
            GPGSBinder.Inst.Logout();

        if (GUILayout.Button("SaveCloud"))
            GPGSBinder.Inst.SaveCloud("mysave", "want data", success => log = $"{success}");

        if (GUILayout.Button("LoadCloud"))
            GPGSBinder.Inst.LoadCloud("mysave", (success, data) => log = $"{success}, {data}");

        if (GUILayout.Button("DeleteCloud"))
            GPGSBinder.Inst.DeleteCloud("mysave", success => log = $"{success}");

        if (GUILayout.Button("ShowAchievementUI"))
            GPGSBinder.Inst.ShowAchievementUI();

        if (GUILayout.Button("UnlockAchievement_one"))
            GPGSBinder.Inst.UnlockAchievement(GPGSIds.achievement_one, success => log = $"{success}");

        if (GUILayout.Button("UnlockAchievement_two"))
            GPGSBinder.Inst.UnlockAchievement(GPGSIds.achievement_two, success => log = $"{success}");

        if (GUILayout.Button("IncrementAchievement_three"))
            GPGSBinder.Inst.IncrementAchievement(GPGSIds.achievement_three, 1, success => log = $"{success}");

        if (GUILayout.Button("ShowAllLeaderboardUI"))
            GPGSBinder.Inst.ShowAllLeaderboardUI();

        if (GUILayout.Button("ShowTargetLeaderboardUI_num"))
            GPGSBinder.Inst.ShowTargetLeaderboardUI(GPGSIds.leaderboard_num);

        if (GUILayout.Button("ReportLeaderboard_num"))
            GPGSBinder.Inst.ReportLeaderboard(GPGSIds.leaderboard_num, 1000, success => log = $"{success}");

        if (GUILayout.Button("LoadAllLeaderboardArray_num"))
            GPGSBinder.Inst.LoadAllLeaderboardArray(GPGSIds.leaderboard_num, scores =>
            {
                log = "";
                for (int i = 0; i < scores.Length; i++)
                    log += $"{i}, {scores[i].rank}, {scores[i].value}, {scores[i].userID}, {scores[i].date}\n";
            });

        if (GUILayout.Button("LoadCustomLeaderboardArray_num"))
            GPGSBinder.Inst.LoadCustomLeaderboardArray(GPGSIds.leaderboard_num, 10,
                GooglePlayGames.BasicApi.LeaderboardStart.PlayerCentered, GooglePlayGames.BasicApi.LeaderboardTimeSpan.Daily, (success, scoreData) =>
                {
                    log = $"{success}\n";
                    var scores = scoreData.Scores;
                    for (int i = 0; i < scores.Length; i++)
                        log += $"{i}, {scores[i].rank}, {scores[i].value}, {scores[i].userID}, {scores[i].date}\n";
                });

        if (GUILayout.Button("IncrementEvent_event"))
            GPGSBinder.Inst.IncrementEvent(GPGSIds.event_event, 1);

        if (GUILayout.Button("LoadEvent_event"))
            GPGSBinder.Inst.LoadEvent(GPGSIds.event_event, (success, iEvent) =>
            {
                log = $"{success}, {iEvent.Name}, {iEvent.CurrentCount}";
            });

        if (GUILayout.Button("LoadAllEvent"))
            GPGSBinder.Inst.LoadAllEvent((success, iEvents) =>
            {
                log = $"{success}\n";
                foreach (var iEvent in iEvents)
                    log += $"{iEvent.Name}, {iEvent.CurrentCount}\n";
            });

        GUILayout.Label(log);
    }
}

전역 인스펙터 컨테이너





GameManager.cs 소스입니다



using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class Container
{
    [SerializeField] Transform _circle; 
    public static Transform circle;

    [SerializeField] Transform _square;
    public static Transform square;

    public void Init() 
    {
        circle = _circle;
        square = _square;
    }
}

public class GameManager : MonoBehaviour
{
    [SerializeField] Container container;

void Awake()
{
        container.Init();
    }
}






Other.cs 소스입니다


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Container;

public class Other : MonoBehaviour
{
    void Start()
    {
        circle.position = Vector3.up;
        square.position = Vector3.down;
    }
}

모노 싱글톤



MonoSingleton<T>.cs 소스입니다


using UnityEngine;

public class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
public static T Inst { get; private set; }
void Awake() => Inst = FindObjectOfType(typeof(T)) as T;
}

AWS RDS와 Lambda로 유니티에서 데이터 주고받기

dbinfo.py 소스입니다


db_host=""
db_username=""
db_password=""
db_name=""
db_port=3306







lambda_function.py 소스입니다


import sys
import logging
import pymysql
import dbinfo
import json

connection = pymysql.connect(host = dbinfo.db_host, port = dbinfo.db_port,
    user = dbinfo.db_username, passwd = dbinfo.db_password, db = dbinfo.db_name)



def lambda_handler(event, context):
    # event['body'] in   command=login&id=idInput&password=passwordInput&info=infoInput

    command = event['body'].split('command=')[1].split('&id=')[0]
    id = event['body'].split('&id=')[1].split('&password=')[0]
    password = event['body'].split('&password=')[1].split('&info=')[0]
    info = event['body'].split('&info=')[1]
    
    if command == 'login':
        return lambda_login(id, password)
    elif command == 'register':
        return lambda_register(id, password)
    elif command == 'save':
        return lambda_save(id, password, info)
    else:
        return {
            'statusCode': 400,
            'body': "Invalid command" 
        }
    
    
    
def lambda_login(id, password):

    query = f"select * from TestTable where BINARY id = '{id}' and BINARY password = '{password}'"
    cursor = connection.cursor()
    cursor.execute(query)
    rows = cursor.fetchall()
  
    if len(rows) == 0:
        return {
            'statusCode': 400,
            'body': "Fail to login" 
        }
    else:
        return {
            'statusCode': 200,
            'body': rows[0][2]
        }
        
        
        
def lambda_register(id, password):

    query = f"select * from TestTable where BINARY id = '{id}'"
    cursor = connection.cursor()
    cursor.execute(query)
    rows = cursor.fetchall()
  
    if len(rows) == 0:
        sql = "INSERT INTO TestTable (id, password) VALUES (%s, %s)"
        val = (id, password)
        cursor.execute(sql, val)
        connection.commit()
        return {
            'statusCode': 200,
            'body': "Register complete" 
        }
    else:
        return {
            'statusCode': 400,
            'body': "Fail to register" 
        }
        
        
        
def lambda_save(id, password, info):

    query = f"select * from TestTable where BINARY id = '{id}' and BINARY password = '{password}'"
    cursor = connection.cursor()
    cursor.execute(query)
    rows = cursor.fetchall()

    query = f"update TestTable set info = '{info}' where BINARY id = '{id}' and BINARY password = '{password}'"
    cursor = connection.cursor()
    cursor.execute(query)
    connection.commit()
    
    if len(rows) == 0:
        return {
                'statusCode': 400,
                'body': "Save fail" 
            }
    else:
        return {
                'statusCode': 200,
                'body': "Save Complete" 
            }








AccountManager.cs 소스입니다


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;

public class AccountManager : MonoBehaviour
{
[SerializeField] InputField idInput;
[SerializeField] InputField passwordInput;
[SerializeField] InputField infoInput;

[SerializeField] string url;

public void LoginClick() => StartCoroutine(AccountCo("login"));

public void RegisterClick() => StartCoroutine(AccountCo("register"));

public void SaveClick() => StartCoroutine(AccountCo("save"));

IEnumerator AccountCo(string command) 
{
WWWForm form = new WWWForm();
form.AddField("command", command);
form.AddField("id", idInput.text);
form.AddField("password", passwordInput.text);
form.AddField("info", infoInput.text);

UnityWebRequest www = UnityWebRequest.Post(url, form);

yield return www.SendWebRequest();
print(www.downloadHandler.text);
}
}


OX 게임만들기

OXManager.cs 소스입니다



using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[System.Serializable]
public class Question 
{
    public string question;
    public bool isO;
}

public class OXManager : MonoBehaviour
{
    [SerializeField] Question[] questions;
    Question question;

    [SerializeField] Text questionText;
    [SerializeField] Text resultText;
    [SerializeField] Text correctText;
    [SerializeField] GameObject confirmParticle;

    [SerializeField] int level;
    int Level 
    {
        get => level;
        set 
        {
            level = value;
            ShowQuestion(value);
        }
    }

    [SerializeField] int correctCount;
    int CorrectCount 
    {
        get => correctCount;
        set 
        {
            correctCount = value;
            correctText.text = $"맞춘개수 : {value}";
        }
    }

    public void ShowQuestion(int level) 
    {
        if (level >= questions.Length)
            return;

        question = questions[level];
        questionText.text = question.question;
    }

    public void AnswerClick(bool isO) 
    {
        if (question.isO == isO)
        {
            StartCoroutine(ResultCo("정답"));
            confirmParticle.SetActive(true);
            Level++;
            CorrectCount++;
        }
        else
        {
            StartCoroutine(ResultCo("틀림"));
            Level++;
        }
    }

void Start()
{
        ShowQuestion(Level);
    }

    IEnumerator ResultCo(string text) 
    {
        resultText.text = text;
        yield return new WaitForSeconds(2);
        resultText.text = "";
    }
}

Photon Server 유니티 멀티게임 만들기 webinar 2탄

GameManager.cs 소스입니다




using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using System;
using UnityEngine.UI;
using PN = Photon.Pun.PhotonNetwork;
using Random = UnityEngine.Random;

public enum State { None, QuickMatching, QuickMatchDone, RacingStart, RacingDone }

public class GameManager : MonoBehaviourPunCallbacks
{
public static GameManager Inst { get; private set; }
void Awake()
{
Inst = this;

ShowPanel("ConnectPanel");

PN.SendRate = 30;
PN.SerializationRate = 15;

if (autoJoin)
ConnectClick(null);

}

[Header("Debug")]
[SerializeField] bool autoJoin;
[SerializeField] byte autoMaxPlayer = 2;

[Header("Panel")]
[SerializeField] GameObject connectPanel;
[SerializeField] GameObject lobbyPanel;
[SerializeField] GameObject gamePanel;

[Header("Lobby")]
[SerializeField] Text quickMatchText;

[Header("Game")]
[SerializeField] Transform[] spawnPoints;
[SerializeField] UnityStandardAssets.Cameras.AutoCam autoCam;
[SerializeField] Text timeText;
[SerializeField] Text countdownText;

public State state;

public int GetIndex
{
get
{
for (int i = 0; i < PN.PlayerList.Length; i++)
{
if (PN.PlayerList[i] == PN.LocalPlayer)
return i;
}
return -1;
}
}
WaitForSeconds one = new WaitForSeconds(1);
int racingStartTime;



public void ShowPanel(string panelName)
{
connectPanel.SetActive(false);
lobbyPanel.SetActive(false);
gamePanel.SetActive(false);

if (panelName == connectPanel.name)
connectPanel.SetActive(true);
else if (panelName == lobbyPanel.name)
lobbyPanel.SetActive(true);
else if (panelName == gamePanel.name)
gamePanel.SetActive(true);
}


public void ConnectClick(InputField nickInput)
{
PN.ConnectUsingSettings();

string nick = nickInput == null ? $"Player{Random.Range(0, 100)}" : nickInput.text;
PN.NickName = nick;
}

public override void OnConnectedToMaster() => PN.JoinLobby();

public override void OnJoinedLobby()
{
ShowPanel("LobbyPanel");

if (autoJoin)
QuickMatchClick();
}

public void QuickMatchClick() 
{
if (state == State.None)
{
state = State.QuickMatching;
quickMatchText.gameObject.SetActive(true);
PN.JoinRandomOrCreateRoom(null, autoMaxPlayer, MatchmakingMode.FillRoom, null, null,
$"room{Random.Range(0, 10000)}", new RoomOptions { MaxPlayers = autoMaxPlayer });
}
else if (state == State.QuickMatching) 
{
state = State.None;
quickMatchText.gameObject.SetActive(false);
PN.LeaveRoom();
}
}

public override void OnJoinedRoom()
{
PlayerChanged();
}

public override void OnPlayerEnteredRoom(Player newPlayer)
{
PlayerChanged();
}

public override void OnPlayerLeftRoom(Player otherPlayer)
{
PlayerChanged();
}

void PlayerChanged()
{
if (PN.CurrentRoom.PlayerCount == autoMaxPlayer) { }
else if (PN.CurrentRoom.PlayerCount != PN.CurrentRoom.MaxPlayers)
return;

StartCoroutine(GameStartCo());
}

IEnumerator GameStartCo() 
{
print("GameStart");
ShowPanel("GamePanel");
SpawnCar();

yield return one;
countdownText.text = "3";
yield return one;
countdownText.text = "2";
yield return one;
countdownText.text = "1";
yield return one;
countdownText.text = "GO";
state = State.RacingStart;
racingStartTime = PN.ServerTimestamp;

yield return one;
yield return one;
countdownText.text = "";
}

void SpawnCar() 
{
GameObject carObj = PN.Instantiate("Car", spawnPoints[GetIndex].position, spawnPoints[GetIndex].rotation);
}

public void SetCamTarget(Transform target) 
{
autoCam.SetTarget(target);
}

void Update()
{
if (state == State.QuickMatching && PN.InRoom)
{
quickMatchText.text = $"{PN.CurrentRoom.PlayerCount} / {PN.CurrentRoom.MaxPlayers}";
}

TimeUpdate();
}

void TimeUpdate() 
{
if (state != State.RacingStart)
return;

TimeSpan elapsedTime = TimeSpan.FromMilliseconds(PN.ServerTimestamp - racingStartTime);
int milliseconds = (int)(elapsedTime.Milliseconds * 0.1f);
timeText.text = $"{elapsedTime.Minutes:D2}:{elapsedTime.Seconds:D2}:{milliseconds:D2}";

}
}










Car.cs 소스입니다



using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;

public class Car : MonoBehaviourPun
{
    [SerializeField] UnityStandardAssets.Vehicles.Car.CarController carController;

    void Start()
    {
        if (photonView.IsMine)
        {
            GameManager.Inst.SetCamTarget(transform);
        }
    }

void Update()
{
        if (photonView.IsMine)
        {
            if (GameManager.Inst.state == State.RacingStart)
            {
                float horizontal = Input.GetAxis("Horizontal");
                float vertical = Input.GetAxis("Vertical");
                float drift = Input.GetAxis("Jump");

                carController.Move(horizontal, vertical, vertical, drift);
            }
            else
                carController.Move(0, 0, 0, 1);
        }
    }
}

벽 콜라이더 생성기




WallColliderGenerator.cs 소스입니다


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

#if UNITY_EDITOR
using UnityEditor;

[CustomEditor(typeof(WallColliderGenerator))]
public class WallColliderGeneratorEditor : Editor
{
WallColliderGenerator wall;

    void OnEnable()
{
wall = target as WallColliderGenerator;
        UnityEditorInternal.ComponentUtility.MoveComponentUp(wall);
        wall.OnEnableCallback();
    }

public override void OnInspectorGUI()
{
        EditorGUI.BeginChangeCheck();

        wall.height = EditorGUILayout.FloatField("Height", wall.height);
        wall.radius = EditorGUILayout.FloatField("Radius", wall.radius);

        if (EditorGUI.EndChangeCheck())
            wall.UpdateHeightRadius();

        if (GUILayout.Button("Clear Collider"))
            wall.ClearCollider();

        if (GUILayout.Button("Create Collider"))
            wall.CreateCollider();
    }
}

#endif

[RequireComponent(typeof(LineRenderer))]
public class WallColliderGenerator : MonoBehaviour
{
    public float height = 3;
    public float radius = 0.5f;
    float Diameter => radius * 2;
    LineRenderer lineRenderer;

    public void OnEnableCallback()
{
        lineRenderer = GetComponent<LineRenderer>();
        lineRenderer.receiveShadows = false;
        lineRenderer.useWorldSpace = false;
        lineRenderer.startWidth = Diameter;
        lineRenderer.endWidth = Diameter;
        lineRenderer.numCornerVertices = 10;
        lineRenderer.numCapVertices = 10;

        if (lineRenderer.sharedMaterial == null)
        {
            var material = new Material(Shader.Find("Sprites/Default"));
            material.color = Color.gray;
            lineRenderer.sharedMaterial = material;
        }
    }

    public void ClearCollider()
    {
        Array.ForEach(GetComponents<CapsuleCollider>(), x => DestroyImmediate(x));

        while (transform.childCount > 0)
            DestroyImmediate(transform.GetChild(0).gameObject);
    }

    public void CreateCollider()
    {
        ClearCollider();

        int size = lineRenderer.positionCount;
        List<Vector3> points = new List<Vector3>();
        
        for (int i = 0; i < size; i++)
        {
            var capsule = gameObject.AddComponent<CapsuleCollider>();
            points.Add(lineRenderer.GetPosition(i));

            capsule.center = lineRenderer.GetPosition(i);
            capsule.height = height;
            capsule.radius = radius;
        }

        for (int i = 0; i < size - 1; i++)
{
            var box = new GameObject().AddComponent<BoxCollider>().transform;
            box.name = "Box";
            box.SetParent(transform);

            box.localPosition = (points[i] + points[i + 1]) * 0.5f;
            box.localRotation = Quaternion.LookRotation(points[i + 1] - points[i]);
            box.localScale = new Vector3(Diameter, height - Diameter, Vector3.Distance(points[i], points[i + 1]));
        }
    }

    public void UpdateHeightRadius()
    {
        Array.ForEach(GetComponents<CapsuleCollider>(), x => { x.radius = radius; x.height = height; });

        for (int i = 0; i < transform.childCount; i++)
        {
            transform.GetChild(i).localScale = new Vector3(Diameter, height - Diameter, transform.GetChild(i).localScale.z);
        }

        lineRenderer.startWidth = Diameter;
        lineRenderer.endWidth = Diameter;
    }
}

텍스쳐 캐싱기법



Test.cs 소스입니다

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

[Serializable]
public class Item
{
    public int code;
    public Texture2D texture;
}

public class Test : MonoBehaviour
{
    [SerializeField] Item[] items;
    [SerializeField] GameObject block;
    [SerializeField] Material blockMaterial;
    [SerializeField] int code;
    Dictionary<int, Material> materialCodeTable = new Dictionary<int, Material>();


    void Update()
    {
        Vector3 rayDirection = Camera.main.ScreenPointToRay(Input.mousePosition).direction;
        if (Physics.Raycast(Camera.main.transform.position, rayDirection, out RaycastHit raycastHit) && Input.GetMouseButtonDown(0))
        {
            Vector3 point = raycastHit.point + raycastHit.normal * 0.5f;
            GameObject blockObj = Instantiate(block, Vector3Int.RoundToInt(point), Quaternion.identity);
            blockObj.GetComponent<Renderer>().material = GetMaterial();
        }
    }

    public Material GetMaterial()
    {
        if (materialCodeTable.ContainsKey(code))
            return materialCodeTable[code];
        else
        {
            Material material = new Material(blockMaterial);
            material.mainTexture = Array.Find(items, x => x.code == code).texture;
            materialCodeTable.Add(code, material);
            return material;
        }
    }
}

스크린샷 카메라 - 유니티 안에서 사진을 추출하세요



CustomUtils.ScreenShot.cs 소스입니다

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;


#if UNITY_EDITOR
using UnityEditor;

[CustomEditor(typeof(CustomUtils.ScreenShot))]
public class ScreenShotEditor : Editor 
{
    CustomUtils.ScreenShot screenShot;
void OnEnable() => screenShot = target as CustomUtils.ScreenShot;

public override void OnInspectorGUI()
{
        base.OnInspectorGUI();
        if (GUILayout.Button("ScreenShot"))
        {
            screenShot.ScreenShotClick();
            EditorApplication.ExecuteMenuItem("Assets/Refresh");
        } 
}
}
#endif

namespace CustomUtils
{
    public class ScreenShot : MonoBehaviour
    {
        [SerializeField] string screenShotName;

        public void ScreenShotClick()
        {
            RenderTexture renderTexture = GetComponent<Camera>().targetTexture;
            Texture2D texture = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.ARGB32, false);
            RenderTexture.active = renderTexture;
            texture.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
            texture.Apply();

            File.WriteAllBytes($"{Application.dataPath}/{screenShotName}.png", texture.EncodeToPNG());
        }
    }
}