This article about how to connection PLC to Unity3D Game Engine using Modbus Serial Communication.
in Unity3D Game Engine using C# (C Sharp) Script for Modbus Serial Communication and as a Modbus Master with serial communication setting 9600-even-8-one.
in PLC (Programmable Logic Controller) using PLC Ladder Programming for Modbus Serial Communication and as a Modbus Slave with Slave Address 1 (one).
In Case Study:
How to Fan speed control with 3D model of Unity3D using PLC input, and
how to turning ON/OFF in PLC outputs using simple 3D model of Unity3D.
Let's watch: my video demonstration on YouTube
 
Free Download : click here
in Unity3D Game Engine using C# (C Sharp) Script for Modbus Serial Communication and as a Modbus Master with serial communication setting 9600-even-8-one.
in PLC (Programmable Logic Controller) using PLC Ladder Programming for Modbus Serial Communication and as a Modbus Slave with Slave Address 1 (one).
In Case Study:
How to Fan speed control with 3D model of Unity3D using PLC input, and
how to turning ON/OFF in PLC outputs using simple 3D model of Unity3D.
Let's watch: my video demonstration on YouTube
PLC Connect to Unity3D Game Engine Using Modbus Serial Communication
Hardware List for PLC and Unity3D Game Engine
- PLC Modbus, I use Siemens S7-200 PLC
 - RS232 PLC Cable, I use Simatic S7-200 RS232 PPI Multi-Master Cable
 - USB to Serial RS232, I use BAFO USB to RS232 Converter
 - Optional : Push Button connect to PLC Input
 
Hardware Connections of PLC and Unity3D Game Engine
Game Engine Software
Unity3D Free Download : https://unity3d.com/get-unity/downloadUnity3D Application for PC Standalone
Unity3D modbus application for Target Platform : Windows and Architecture : x86_64Free Download : click here
Project File for Unity3D Game Engine and PLC:
- Unity3D Project File : click here
 - PLC Ladder Programming for Siemens S7-200 : click here
 
Input/output on Siemens PLC
- Input I0.0 for Fan Speed is -400
 - Input I0.1 for Fan Speed is Increments -300
 - Input I0.2 for Fan Speed is Increments 300
 - Input I0.3 for Fan Speed is 400
 - Input I0.4 for Fan Speed is 0 or Fan Stop
 - If cylinder model clicked in Unity3D then all PLC output QB0 is ON
 
Unity3D Scripts
Unity3D c#
serialport.csusing UnityEngine;
using System.Collections;
using System.IO.Ports;
public class serialport : MonoBehaviour {
    private SerialPort sp;
    private const int BUFFER_SIZE = 128;
    private byte[] frame16 = new byte[BUFFER_SIZE];
    private byte[] frame03 = new byte[BUFFER_SIZE];
    private byte state=0;
    public string PortName ="COM1";
    private float Scan_Time = 0.03f; //0.03 Seconds
    public byte Connection_Status = 0;
    //FUNCTION 03
    public long F03_requests = 0;
    public long F03_successful_requests = 0;
    public long F03_failed_requests = 0;
    public long F03_exception_errors =0;
    private byte F03_Slave_Address = 1;
    private byte F03_Function = 3;
    private int F03_Starting_Address = 0;
    private int F03_NumberofRegisters = 2;
    private byte F03_Byte_Count;
    public int[] F03_values;
    //FUNCTION 16
    public long F16_requests = 0;
    public long F16_successful_requests = 0;
    public long F16_failed_requests = 0;
    public long F16_exception_errors =0;
    private byte F16_Slave_Address = 1;
    private byte F16_Function = 16;
    private int F16_Starting_Address = 0;
    private int F16_NumberofRegisters = 1;
    private byte F16_Byte_Count;
    public int[] F16_values;
    void OnGUI() {
        GUI.Label(new Rect(10, Screen.height-35, Screen.width, 35),"MODBUS SLAVE=1, SERIAL PORT=" + PortName + ", BAUD=9600, PARITY=EVEN, DATA=8BIT, STOPBITS=ONE");
    }
    void Start () {
        Application.runInBackground = true;
        F16_values = new int[F16_NumberofRegisters];
        F03_values = new int[F03_NumberofRegisters];
        bool Existing = false;
        foreach(string str in SerialPort.GetPortNames())
        {
            if(str==PortName)Existing = true;
        }
        if (Existing) {
            //Debug.Log(string.Format("Existing COM port: {0}", PortName));
        }else{
            //Debug.LogError(string.Format("Not Existing COM port: {0}", PortName));
            Connection_Status = 5;
            return;
        }
        sp = new SerialPort( PortName
                            , 9600
                            , Parity.Even
                            , 8
                            , StopBits.One);
        OpenConnection();
        state=0;
        InvokeRepeating("Modbus_Update", 1.0f, Scan_Time);
    }
    
    public void OpenConnection() {
        if (sp != null) 
        {
            if (sp.IsOpen) 
            {
                sp.Close();
                print(PortName + " Closing port, because it was already open!");
                Connection_Status = 1;
            }
            else 
            {
                sp.Open(); 
                sp.ReadTimeout = 1;  
                sp.WriteTimeout = 10;  
                print(PortName + " Port Opened!");
                sp.BaseStream.Flush();
                Connection_Status = 2;
            }
        }
        else 
        {
            if (sp.IsOpen)
            {
                print(PortName + " Port is already open");
                Connection_Status = 3;
            }
            else 
            {
                print(PortName + " Port == null");
                Connection_Status = 4;
            }
        }
    }
    void OnApplicationQuit() {
        if (sp != null) {
            if (sp.IsOpen)sp.Close ();    
        }
    }
    void end() {
        if (sp != null) {
            if (sp.IsOpen)sp.Close ();    
        }
    }
    int calculateCRC(byte[] crcframe,int bufferSize) 
    {
        int temp, temp2, flag;
        temp = 0xFFFF;
        for (int i = 0; i < bufferSize; i++)
        {
            temp = temp ^ crcframe[i];
            for (int j = 1; j <= 8; j++)
            {
                flag = temp & 0x0001;
                temp >>= 1;
                if (flag==1)
                    temp ^= 0xA001;
            }
        }
        temp2 = temp >> 8;
        temp = (temp << 8) | temp2;
        temp &= 0xFFFF;
        return temp; 
    }
    void Func16(){
        //Debug.Log("FUNCTION 16");
        F16_requests++;
        F16_NumberofRegisters = (int)(F16_values.Length);
        if (F16_NumberofRegisters < 1)F16_NumberofRegisters = 1;
        if (F16_Starting_Address < 0)F16_Starting_Address = 0;
        F16_Byte_Count = (byte)(F16_NumberofRegisters * 2);
        int frameSize = 9 + (2 * F16_NumberofRegisters);
        frame16[0] = F16_Slave_Address;
        frame16[1] = F16_Function;
        frame16[2] = (byte)(F16_Starting_Address >> 8);
        frame16[3] = (byte)F16_Starting_Address;
        frame16[4] = (byte)(F16_NumberofRegisters >> 8);
        frame16[5] = (byte)F16_NumberofRegisters;
        frame16[6] = F16_Byte_Count;
        for (int i = 0; i < F16_NumberofRegisters; i++)
        {
            frame16[7 + 2 * i] = (byte)(F16_values[i] >> 8);
            frame16[8 + 2 * i] = (byte)(F16_values[i]);
        }
        int crc16 = calculateCRC(frame16, frameSize - 2);
        byte crcHi, crcLo;
        crcHi = (byte)((crc16 >> 8)& 0xFF);
        crcLo = (byte)(crc16 & 0xFF);
        frame16[frameSize - 2] = crcHi;
        frame16[frameSize - 1] = crcLo;
        try {            
            sp.Write(frame16, 0, frameSize);
        }        
        catch (System.Exception ex) {            
            if(ex.Message.Contains("The device does not recognize the command")){
                //Debug.LogError(string.Format("Not Existing COM port: {0}", PortName));
                //Application.Quit();
                Connection_Status = 5;
                return;
            }
            //Debug.LogWarning("Func16 " + ex.Message); 
            F16_exception_errors++;
            Connection_Status = 6;
            return;
        }
        sp.BaseStream.Flush();
    }
    void RecvFunc16(){
        int Size = 0;
        byte tempB = 0;
        try {
            tempB =  (byte) sp.ReadByte();
            while (tempB == F16_Slave_Address) {
                while (Size<8) {
                    if(Size<BUFFER_SIZE)frame16[Size] = (byte) (tempB);
                    if(Size==7)break;
                    tempB = (byte) sp.ReadByte();
                    Size++;
                    Connection_Status = 100;
                }
                break;
            }
        }
        catch (System.Exception ex) {
            if(ex.Message.Contains("The device does not recognize the command")){
                //Debug.LogError(string.Format("Not Existing COM port: {0}", PortName));
                //Application.Quit();
                Connection_Status = 5;
                return;
            }
            //Debug.LogWarning("RecvFunc16 " + ex.Message);
            F16_exception_errors++;
            Connection_Status = 6;
            return;
        }
        
        if(Size==7){
            if(frame16[0]==F16_Slave_Address && frame16[1]==F16_Function){
                int crc16 = calculateCRC(frame16, Size - 1);
                int received_crc = ((frame16[Size - 1] << 8) | frame16[Size]); 
                if(crc16==received_crc){
                    F16_successful_requests++;
                    Connection_Status = 101;
                    return;
                }
            }
        }
        F16_failed_requests++;
    }
    void Func03(){
        //Debug.Log("FUNCTION 03");
        F03_requests++;
        int frameSize = 8;
        F03_NumberofRegisters = (int) (F03_values.Length);
        if (F03_NumberofRegisters < 1)F03_NumberofRegisters = 1;
        if (F03_Starting_Address < 0)F03_Starting_Address = 0;
        F03_Byte_Count = (byte)(F03_NumberofRegisters * 2);
        frame03[0] = F03_Slave_Address;
        frame03[1] = F03_Function;
        frame03[2] = (byte)(F03_Starting_Address >> 8);
        frame03[3] = (byte)F03_Starting_Address;
        frame03[4] = (byte)(F03_NumberofRegisters >> 8);
        frame03[5] = (byte)F03_NumberofRegisters;
        
        
        int crc16 = calculateCRC(frame03, frameSize - 2);
        byte crcHi, crcLo;
        crcHi = (byte)((crc16 >> 8)& 0xFF);
        crcLo = (byte)(crc16 & 0xFF);
        frame03[frameSize - 2] = crcHi;
        frame03[frameSize - 1] = crcLo;
        
        try {             
            sp.Write(frame03, 0, frameSize);
        }        
        catch (System.Exception ex)    {    
            if(ex.Message.Contains("The device does not recognize the command")){
                //Debug.LogError(string.Format("Not Existing COM port: {0}", PortName));
                //Application.Quit();
                Connection_Status = 5;
                return;
            }
            //Debug.LogWarning("Func03 " + ex); 
            F03_exception_errors++;
            Connection_Status = 6;
            return;
        }
        
        sp.BaseStream.Flush();    
        
    }
    void RecvFunc03(){
        int Size = 0;
        byte tempB = 0;
        int maxrev = 5 + F03_Byte_Count;
        try {
            tempB =  (byte) sp.ReadByte();
            while (tempB == F03_Slave_Address) {
                while (Size<maxrev) {
                    frame03[Size] = (byte) (tempB);
                    if(Size==(maxrev-1))break;
                    tempB = (byte) sp.ReadByte();
                    Size++;
                    Connection_Status = 100;
                }
                break;
            }            
        }
        catch (System.Exception ex) {
            if(ex.Message.Contains("The device does not recognize the command")){
                //Debug.LogError(string.Format("Not Existing COM port: {0}", PortName));
                //Application.Quit();
                Connection_Status = 5;
                return;
            }
            //Debug.LogWarning("RecvFunc03 " + ex.Message);
            F03_exception_errors++;
            Connection_Status = 6;
            return;
        }
        
        if(Size==(maxrev-1)){
            if(frame03[0]==F03_Slave_Address && frame03[1]==F03_Function){
                int crc16 = calculateCRC(frame03, Size - 1);
                int received_crc = ((frame03[Size - 1] << 8) | frame03[Size]); 
                if(crc16==received_crc && frame03[2]==F03_Byte_Count){
                    int index = 3;
                    for (int i = 0; i < F03_NumberofRegisters; i++)
                    {
                        F03_values[i] = (int)((frame03[index] << 8) | frame03[index + 1]); 
                        index += 2;
                        Connection_Status = 102;
                    }
                    F03_successful_requests++;
                    return;
                }
            }
        }
        F03_failed_requests++;
    }
    void Modbus_Update() 
    {
        switch (state)
        {
        case 0:
            Func16();
            state=1;
            break;
        case 1:
            RecvFunc16();
            state=2;
            break;
        case 2:
            Func03();
            state=3;
            break;
        case 3:
            RecvFunc03();
            state=0;
            break;
        }
    }
}
Unity3D c#
moveobject.csusing UnityEngine;
using System.Collections;
public class moveobject : MonoBehaviour {
    public GameObject SerialportObject;
    public serialport script;
    public int fan_speed = 0;
    private short modbus_read;
    private short modbus_write;
    private bool conn = false;
    void OnGUI() {
        GUI.Label(new Rect((Screen.width/2)-75, 10, 150, 25),"program-plc.blogspot.com");
        switch (script.Connection_Status)
        {
        case 0:
            GUI.Label(new Rect(10, 40, Screen.width, 25),"Connection Status : Common Error");
            break;            
        case 1:
            GUI.Label(new Rect(10, 40,Screen.width, 25),"Connection Status : Closing port, because it was already open!");
            break;            
        case 2:
            GUI.Label(new Rect(10, 40,Screen.width, 25),"Connection Status : Port Opened!");
            break;
        case 3:
            GUI.Label(new Rect(10, 40,Screen.width, 25),"Connection Status : Port is already open");
            break;
        case 4:
            GUI.Label(new Rect(10, 40,Screen.width, 25),"Connection Status : Port == null");
            break;
        case 5:
            GUI.Label(new Rect(10, 40,Screen.width, 25),"Connection Status : Not Existing " + script.PortName.ToString() + " , Please ReStart");
            break;
        case 6:
            GUI.Label(new Rect(10, 40,Screen.width, 25),"Connection Status : Exception Errors");
            break;
        case 100:
            GUI.Label(new Rect(10, 40, Screen.width, 25),"Connection Status : OK");
            conn = true;
            break;
        case 101:
            GUI.Label(new Rect(10, 40, Screen.width, 25),"Connection Status : OK");
            conn = true;
            break;
        case 102:
            GUI.Label(new Rect(10, 40, Screen.width, 25),"Connection Status : OK");
            conn = true;
            break;
        }
        GUI.Label(new Rect(10, 70, 250, 25),"Modbus 40001 <= Push Button : " + modbus_write.ToString());
        GUI.Label(new Rect(10, 100, 250, 25),"Modbus 40002 => Fan Speed : " + modbus_read.ToString());
    }
    void Start () {
        script = SerialportObject.GetComponent<serialport>();
    }
    
    void Update () {
        if (Input.GetMouseButtonDown(0) && conn) 
        {
            GameObject PB;
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if(Physics.Raycast(ray,out hit))
            {
                if(hit.collider.gameObject.name== "Push_Button"){
                    PB = GameObject.Find(hit.collider.gameObject.name);
                    PB.transform.localPosition = new Vector3(0, 0.4f,0);
                    if(script.F16_values.Length>0){
                        script.F16_values[0]=1;
                    }
                }
            }
        }    
        if (Input.GetMouseButtonUp(0)){
            GameObject PB;
            PB = GameObject.Find("Push_Button");
            PB.transform.localPosition = new Vector3(0, 1.2f,0);
            if(script.F16_values.Length>0){
                script.F16_values[0]=0;
            }
        }
        if (script.F03_values.Length > 1){
            modbus_write = (short) script.F03_values [0];
            modbus_read = (short) script.F03_values [1];
            fan_speed = modbus_read;
        }
        if (fan_speed !=0){
            GameObject FAN;
            FAN = GameObject.Find("Fan");
            FAN.transform.Rotate(0,fan_speed*Time.deltaTime,0);
        }
    }
}

