2015-01-23 10:20:32 book_longssl 阅读数 2219
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4641 人正在学习 去看看 张刚

1,创建一个数据库表,我们就以 test数据库为例,数据库里建一个tb_User表, tb_User里有User_Name , User_Pass 字段.

2,创建一个验证用户基本信息的asp.net页面,页面名字是:Default.aspx

后台代码如下:
  1. using System;
  2. using System.Configuration;
  3. using System.Data;
  4. using System.Web;
  5. using System.Web.Security;
  6. using System.Web.UI;
  7. using System.Web.UI.HtmlControls;
  8. using System.Web.UI.WebControls;
  9. using System.Web.UI.WebControls.WebParts;
  10. using System.Data.SqlClient;

  11. public partial class _Default : System.Web.UI.Page
  12. {

  13. SqlConnection conn = new SqlConnection("Data Source=.sqlexpress2008;Initial Catalog=test;Integrated Security=True");

  14. protected void Page_Load(object sender, EventArgs e)
  15. {
  16. string name = Request.Form["name2";
  17. string pwd=Request.Form["pwd";

  18. conn.Open();

  19. string sql = "select User_Name from tb_User where User_Name='" + name + "' and User_Pass='"+pwd+"'";
  20. SqlCommand cmd = new SqlCommand(sql, conn);
  21. if (cmd.ExecuteScalar() != null)
  22. Response.Write("success");
  23. else
  24. Response.Write("faield");
  25. conn.Close();
  26. }
  27. }
复制代码



前台代码不用管.

3,新建一个Unity 3D项目,新建一个Javascript文件, 在Javascript文件上编写如下代码:

  1. var URL = "http://devmac.net/domino/rui/Default.aspx"; //提交的URL地址
  2. static var return_data:String;
  3. var strname:String;
  4. var strpwd:String;
  5. var cls:boolean=false;
  6. var xx:Rect = Rect (20, 10, 150, 100);
  7. function OnGUI() {
  8. if(cls)
  9. xx = GUI.Window(1, xx, windowjj, "Window");
  10. GUI.Label(Rect(10,10,80,20),"UserName:");
  11. GUI.Label(Rect(10,30,80,20),"Userpass:");
  12. strname=GUI.TextField(Rect(90,10,100,20),strname);
  13. strpwd=GUI.PasswordField(Rect(90,30,100,20),strpwd,"*"[0],25);
  14. //same as above, but for password
  15. if ( GUI.Button ( Rect (60, 60, 100, 20) , "Login" ) ){ //just a button
  16. Login();
  17. }
  18. }
  19. function windowjj(windowID : int){
  20. GUI.Label(Rect(55,25,50,20),return_data);
  21. if(GUI.Button(Rect(50,55,50,20),"Close")) cls=false;//
  22. }
  23. function Login() {
  24. var form = new WWWForm(); //创建一个WWWForm对象。
  25. form.AddField( "name2", strname );
  26. form.AddField("pwd",strpwd);
  27. var w:WWW=new WWW(URL,form);
  28. yield w; //we wait for the form to check the PHP file, so our game dont just hang
  29. cls=true;
  30. if (w.error != null) {
  31. //if there is an error, tell us
  32. return_data=w.error;
  33. } else {
  34. return_data=w.text;
  35. w.Dispose(); //clear our form in game
  36. }
  37. }
复制代码


4. 此代码放在主相机上。

5.最后在网上搜一个crossdomain.xml文件,自己写也行,把它放在挂有Asp.net网站的根目录下,这样就大功告成了.



6,导入到IPhone手机里也成功访问到数据库.

2015-06-02 12:13:27 qq15233635728 阅读数 9768
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4641 人正在学习 去看看 张刚

利用Unity3d制作完毕游戏发布到appstore,有时会做游戏内购买虚拟物品,也就是内购。

在Ios开发中叫做:In App Purchase,简称IAP

那么如何在unity3d内嵌入IPA呢?几经辗转,多方搜索,摸索出点经验,分享给大家,如有疏漏,还请指教。

当然也有人们写好的插件可用,我觉得自己写的才用着顺手。

 

一、准备条件:

1、 申请苹果开发者账号。后台先创建证书,在创建应用,填写应用详情,创建测试用的账号,创建内购项目。

这里根据需要创建consumable(每次都需要购买)或者non-consumable的(购买一次一直可用,就是如果买过可以恢复购买)内购项目。

如下我们创建了non-consumable类型,名称“Package_2”,这个名称仅能使用一次,即使删除了也不能够再次利用。


 

创建测试人员账号,可以免费测试购买本开发者账号下面所有应用的物品:


 

2、简单了解ios开发的Object-C语言,主要是用来做内购,详细请百度:

3、简单了解IPA

详细请参见:StoreKit Guide(In App Purchase)翻译

http://yarin.blog.51cto.com/1130898/549141

4、了解unity3d与ios通信,详细参见:为iOS创建插件 Building Plugins for iOS

http://game.ceeger.com/Manual/PluginsForIOS.html

 

二、下面我们单独创建一个例子来演示:

1、 创建工程,切换到ios平台、创建空gameobject,改名为Main,创建点击按钮触发购买的脚本,挂在Main上面。创建平台文件,下面创建子文件夹IOS。

2、 在设置里面修改包名,改为你自己在appstore后台创建的名称


IPADemo里面编写与ios通信代码以及购买代码,其中内购商品名称修改为自己appstore后台定义的:private stringproduct = "Package_2";

 

using UnityEngine;

using System.Collections;

using System.Collections.Generic;

using System.Runtime.InteropServices;

 

public class IPADemo : MonoBehaviour {

 

         publicList<string> productInfo = new List<string>();

 

         privatestring product = "Package_2";

        

         [DllImport("__Internal")]

         privatestatic extern void TestMsg();//测试信息发送

        

         [DllImport("__Internal")]

         privatestatic extern void TestSendString(string s);//测试发送字符串

        

         [DllImport("__Internal")]

         privatestatic extern void TestGetString();//测试接收字符串

        

         [DllImport("__Internal")]

         privatestatic extern void InitIAPManager();//初始化

        

         [DllImport("__Internal")]

         privatestatic extern bool IsProductAvailable();//判断是否可以购买

        

         [DllImport("__Internal")]

         privatestatic extern void RequstProductInfo(string s);//获取商品信息

        

         [DllImport("__Internal")]

         privatestatic extern void BuyProduct(string s);//购买商品

        

         //测试从xcode接收到的字符串

         voidIOSToU(string s)

         {

                   Debug.Log("[MsgFrom ios]"+s);

         }

        

         //获取product列表

         voidShowProductList(string s){

                   productInfo.Add(s);

         }

        

         //获取商品回执

         voidProvideContent(string s)

         {

                   Debug.Log("[MsgFrom ios]proivideContent : "+s);

         }

         voidStart ()

         {

                   InitIAPManager();

         }

         voidUpdate ()

         {                          

         }

        

         voidOnGUI()

         {                

                   if(Btn("GetProducts")){

                            if(!IsProductAvailable())

                                     thrownew System.Exception("IAP not enabled");

                            productInfo= new List<string>();

                            RequstProductInfo(product);

                   }

                  

                   GUILayout.Space(40);

                  

                   for(inti=0; i<productInfo.Count; i++){

                            if(GUILayout.Button(productInfo[i],GUILayout.Height (100), GUILayout.MinWidth (200))){

                                     string[]cell = productInfo[i].Split('\t');

                                     Debug.Log("[Buy]"+cell[cell.Length-1]);

                                     BuyProduct(cell[cell.Length-1]);

                            }

                   }

         }

        

         boolBtn(string msg){

                   GUILayout.Space(100);

                   return       GUILayout.Button (msg,GUILayout.Width(200),GUILayout.Height(100));

         }

}

 

还需要在xcode里面编写内购代码然后复制到平台下,ios文件夹下:


只能在真机上才能出现内购窗,Unity中运行效果如下:


 

3、导出ios工程,在mac上xcode中打开:

4、加入依赖项,libz,storekit:


5、在真机上运行,首先获得商品列表,然后点击购买,然后在弹出的账号密码框里面修改为沙盒测试账号。即可测试购买成功。


 2个.h文件分别为:

1、IAPInterface.h


#import <Foundation/Foundation.h>


@interface IAPInterface : NSObject


@end


2、IAPManager.h


#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>


@interface IAPManager : NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>{
    SKProduct *proUpgradeProduct;
    SKProductsRequest *productsRequest;
}


-(void)attachObserver;
-(BOOL)CanMakePayment;
-(void)requestProductData:(NSString *)productIdentifiers;
-(void)buyRequest:(NSString *)productIdentifier;


@end


 2个.m文件分别为:

1/IAPManager.m

#import "IAPManager.h"

@implementation IAPManager


-(void) attachObserver{
    NSLog(@"AttachObserver");
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}


-(BOOL) CanMakePayment{
    return [SKPaymentQueue canMakePayments];
}


-(void) requestProductData:(NSString *)productIdentifiers{
    NSArray *idArray = [productIdentifiers componentsSeparatedByString:@"\t"];
    NSSet *idSet = [NSSet setWithArray:idArray];
    [self sendRequest:idSet];
}


-(void)sendRequest:(NSSet *)idSet{
    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:idSet];
    request.delegate = self;
    [request start];
}


-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    NSArray *products = response.products;
    
    for (SKProduct *p in products) {
        UnitySendMessage("Main", "ShowProductList", [[self productInfo:p] UTF8String]);
    }
    
    for(NSString *invalidProductId in response.invalidProductIdentifiers){
        NSLog(@"Invalid product id:%@",invalidProductId);
    }
    
    [request autorelease];
}


-(void)buyRequest:(NSString *)productIdentifier{
    SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}


-(NSString *)productInfo:(SKProduct *)product{
    NSArray *info = [NSArray arrayWithObjects:product.localizedTitle,product.localizedDescription,product.price,product.productIdentifier, nil];
    
    return [info componentsJoinedByString:@"\t"];
}


-(NSString *)transactionInfo:(SKPaymentTransaction *)transaction{
    
    return [self encode:(uint8_t *)transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length];
    
    //return [[NSString alloc] initWithData:transaction.transactionReceipt encoding:NSASCIIStringEncoding];
}


-(NSString *)encode:(const uint8_t *)input length:(NSInteger) length{
    static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    
    NSMutableData *data = [NSMutableData dataWithLength:((length+2)/3)*4];
    uint8_t *output = (uint8_t *)data.mutableBytes;
    
    for(NSInteger i=0; i<length; i+=3){
        NSInteger value = 0;
        for (NSInteger j= i; j<(i+3); j++) {
            value<<=8;
            
            if(j<length){
                value |=(0xff & input[j]);
            }
        }
        
        NSInteger index = (i/3)*4;
        output[index + 0] = table[(value>>18) & 0x3f];
        output[index + 1] = table[(value>>12) & 0x3f];
        output[index + 2] = (i+1)<length ? table[(value>>6) & 0x3f] : '=';
        output[index + 3] = (i+2)<length ? table[(value>>0) & 0x3f] : '=';
    }
    
    return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
}


-(void) provideContent:(SKPaymentTransaction *)transaction{
    UnitySendMessage("Main", "ProvideContent", [[self transactionInfo:transaction] UTF8String]);
}


-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:
                [self completeTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
                break;
            default:
                break;
        }
    }
}


-(void) completeTransaction:(SKPaymentTransaction *)transaction{
    NSLog(@"Comblete transaction : %@",transaction.transactionIdentifier);
    [self provideContent:transaction];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}


-(void) failedTransaction:(SKPaymentTransaction *)transaction{
    NSLog(@"Failed transaction : %@",transaction.transactionIdentifier);
    
    if (transaction.error.code != SKErrorPaymentCancelled) {
        NSLog(@"!Cancelled");
    }
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}


-(void) restoreTransaction:(SKPaymentTransaction *)transaction{
    NSLog(@"Restore transaction : %@",transaction.transactionIdentifier);
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}




@end


2、#import "IAPInterface.h"
#import "IAPManager.h"


@implementation IAPInterface


void TestMsg(){
    NSLog(@"Msg received");


}


void TestSendString(void *p){
    NSString *list = [NSString stringWithUTF8String:p];
    NSArray *listItems = [list componentsSeparatedByString:@"\t"];
    
    for (int i =0; i<listItems.count; i++) {
        NSLog(@"msg %d : %@",i,listItems[i]);
    }
    
}


void TestGetString(){
    NSArray *test = [NSArray arrayWithObjects:@"t1",@"t2",@"t3", nil];
    NSString *join = [test componentsJoinedByString:@"\n"];
    
    
    UnitySendMessage("Main", "IOSToU", [join UTF8String]);
}


IAPManager *iapManager = nil;


void InitIAPManager(){
    iapManager = [[IAPManager alloc] init];
    [iapManager attachObserver];
    
}


bool IsProductAvailable(){
    return [iapManager CanMakePayment];
}


void RequstProductInfo(void *p){
    NSString *list = [NSString stringWithUTF8String:p];
    NSLog(@"productKey:%@",list);
    [iapManager requestProductData:list];
}


void BuyProduct(void *p){
    [iapManager buyRequest:[NSString stringWithUTF8String:p]];
}


@end


2013-10-13 11:08:06 jbjwpzyl3611421 阅读数 15550
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4641 人正在学习 去看看 张刚

最近比较忙,有段时间没写博客拉。最近项目中需要使用HTTP与Socket,雨松MOMO把自己这段时间学习的资料整理一下。有关Socket与HTTP的基础知识MOMO就不赘述拉,不懂得朋友自己谷歌吧。我们项目的需求是在登录的时候使用HTTP请求,游戏中其它的请求都用Socket请求,比如人物移动同步坐标,同步关卡等等。

1.Socket

        Socket不要写在脚本上,如果写在脚本上游戏场景一旦切换,那么这条脚本会被释放掉,Socket会断开连接。场景切换完毕后需要重新在与服务器建立Socket连接,这样会很麻烦。所以我们需要把Socket写在一个单例的类中,不用继承MonoBehaviour。这个例子我模拟一下,主角在游戏中移动,时时向服务端发送当前坐标,当服务器返回同步坐标时角色开始同步服务端新角色坐标。

Socket在发送消息的时候采用的是字节数组,也就是说无论你的数据是 int float short object 都会将这些数据类型先转换成byte[] , 目前在处理发送的地方我使用的是数据包,也就是把(角色坐标)结构体object转换成byte[]发送, 这就牵扯一个问题, 如何把结构体转成字节数组, 如何把字节数组回转成结构体。请大家接续阅读,答案就在后面,哇咔咔。

直接上代码

JFSocket.cs 该单例类不要绑定在任何对象上

using UnityEngine;   
using System.Collections;   
using System;   
using System.Threading;   
using System.Text;   
using System.Net;   
using System.Net.Sockets;   
using System.Collections.Generic;   
using System.IO;   
using System.Runtime.InteropServices;   
using System.Runtime.Serialization;   
using System.Runtime.Serialization.Formatters.Binary;   
       
public class JFSocket   
{   
       
    //Socket客户端对象   
    private Socket clientSocket;   
    //JFPackage.WorldPackage是我封装的结构体,   
    //在与服务器交互的时候会传递这个结构体   
    //当客户端接到到服务器返回的数据包时,我把结构体add存在链表中。   
    public List<JFPackage.WorldPackage> worldpackage;   
    //单例模式   
    private static JFSocket instance;   
    public static JFSocket GetInstance()   
    {   
        if (instance == null)   
        {   
            instance = new JFSocket();   
        }   
        return instance;   
     }         
       
    //单例的构造函数   
    JFSocket()   
    {   
        //创建Socket对象, 这里我的连接类型是TCP   
        clientSocket = new Socket (AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);   
        //服务器IP地址   
        IPAddress ipAddress = IPAddress.Parse ("192.168.1.100");   
        //服务器端口   
        IPEndPoint ipEndpoint = new IPEndPoint (ipAddress, 10060);   
        //这是一个异步的建立连接,当连接建立成功时调用connectCallback方法   
        IAsyncResult result = clientSocket.BeginConnect (ipEndpoint,new AsyncCallback (connectCallback),clientSocket);   
        //这里做一个超时的监测,当连接超过5秒还没成功表示超时   
        bool success = result.AsyncWaitHandle.WaitOne( 5000, true );   
        if ( !success )   
        {   
            //超时   
            Closed();   
            Debug.Log("connect Time Out");   
        }else
        {   
            //与socket建立连接成功,开启线程接受服务端数据。   
            worldpackage = new List<JFPackage.WorldPackage>();   
            Thread thread = new Thread(new ThreadStart(ReceiveSorket));   
            thread.IsBackground = true;   
            thread.Start();   
        }   
    }   
       
    private void connectCallback(IAsyncResult asyncConnect)   
    {   
        Debug.Log("connectSuccess");   
    }   
       
    private void ReceiveSorket()   
    {   
        //在这个线程中接受服务器返回的数据   
        while (true)   
        {    
       
            if(!clientSocket.Connected)   
            {   
                //与服务器断开连接跳出循环   
                Debug.Log("Failed to clientSocket server.");   
                clientSocket.Close();   
                break;   
            }   
            try
            {   
                //接受数据保存至bytes当中   
                byte[] bytes = new byte[4096];   
                //Receive方法中会一直等待服务端回发消息   
                //如果没有回发会一直在这里等着。   
                int i = clientSocket.Receive(bytes);   
                if(i <= 0)   
                {   
                    clientSocket.Close();   
                    break;   
                }      
       
                //这里条件可根据你的情况来判断。   
                //因为我目前的项目先要监测包头长度,   
                //我的包头长度是2,所以我这里有一个判断   
                if(bytes.Length > 2)   
                {   
                    SplitPackage(bytes,0);   
                }else
                {   
                    Debug.Log("length is not  >  2");   
                }   
       
             }   
             catch (Exception e)   
             {   
                Debug.Log("Failed to clientSocket error." + e);   
                clientSocket.Close();   
                break;   
             }   
        }   
    }      
       
    private void SplitPackage(byte[] bytes , int index)   
    {   
        //在这里进行拆包,因为一次返回的数据包的数量是不定的   
        //所以需要给数据包进行查分。   
        while(true)   
        {   
            //包头是2个字节   
            byte[] head = new byte[2];   
            int headLengthIndex = index + 2;   
            //把数据包的前两个字节拷贝出来   
            Array.Copy(bytes,index,head,0,2);   
            //计算包头的长度   
            short length = BitConverter.ToInt16(head,0);   
            //当包头的长度大于0 那么需要依次把相同长度的byte数组拷贝出来   
            if(length > 0)   
            {   
                byte[] data = new byte[length];   
                //拷贝出这个包的全部字节数   
                Array.Copy(bytes,headLengthIndex,data,0,length);   
                //把数据包中的字节数组强制转换成数据包的结构体   
                //BytesToStruct()方法就是用来转换的   
                //这里需要和你们的服务端程序商量,   
                JFPackage.WorldPackage wp = new JFPackage.WorldPackage();   
                wp = (JFPackage.WorldPackage)BytesToStruct(data,wp.GetType());   
                //把每个包的结构体对象添加至链表中。   
                worldpackage.Add(wp);   
                //将索引指向下一个包的包头   
                index  =  headLengthIndex + length;   
       
            }else
            {   
                //如果包头为0表示没有包了,那么跳出循环   
  
                break;   
            }   
        }   
    }      
       
    //向服务端发送一条字符串   
    //一般不会发送字符串 应该是发送数据包   
    public void SendMessage(string str)   
    {   
        byte[] msg = Encoding.UTF8.GetBytes(str);   
       
        if(!clientSocket.Connected)   
        {   
            clientSocket.Close();   
            return;   
        }   
        try
        {   
            //int i = clientSocket.Send(msg);   
            IAsyncResult asyncSend = clientSocket.BeginSend (msg,0,msg.Length,SocketFlags.None,new AsyncCallback (sendCallback),clientSocket);   
            bool success = asyncSend.AsyncWaitHandle.WaitOne( 5000, true );   
            if ( !success )   
            {   
                clientSocket.Close();   
                Debug.Log("Failed to SendMessage server.");   
            }   
        }   
        catch
        {   
             Debug.Log("send message error" );   
        }   
    }   
       
    //向服务端发送数据包,也就是一个结构体对象   
    public void SendMessage(object obj)   
    {   
       
        if(!clientSocket.Connected)   
        {   
            clientSocket.Close();   
            return;   
        }   
        try
        {   
            //先得到数据包的长度   
            short size = (short)Marshal.SizeOf(obj);   
            //把数据包的长度写入byte数组中   
            byte [] head = BitConverter.GetBytes(size);   
            //把结构体对象转换成数据包,也就是字节数组   
            byte[] data = StructToBytes(obj);   
       
            //此时就有了两个字节数组,一个是标记数据包的长度字节数组, 一个是数据包字节数组,   
            //同时把这两个字节数组合并成一个字节数组   
       
            byte[] newByte = new byte[head.Length + data.Length];   
            Array.Copy(head,0,newByte,0,head.Length);   
            Array.Copy(data,0,newByte,head.Length, data.Length);   
       
            //计算出新的字节数组的长度   
            int length = Marshal.SizeOf(size) + Marshal.SizeOf(obj);   
       
            //向服务端异步发送这个字节数组   
            IAsyncResult asyncSend = clientSocket.BeginSend (newByte,0,length,SocketFlags.None,new AsyncCallback (sendCallback),clientSocket);   
            //监测超时   
            bool success = asyncSend.AsyncWaitHandle.WaitOne( 5000, true );   
            if ( !success )   
            {   
                clientSocket.Close();   
                Debug.Log("Time Out !");   
            }    
       
        }   
        catch (Exception e)   
        {   
             Debug.Log("send message error: " + e );   
        }   
    }   
       
    //结构体转字节数组   
    public byte[] StructToBytes(object structObj)   
    {   
       
        int size = Marshal.SizeOf(structObj);   
        IntPtr buffer =  Marshal.AllocHGlobal(size);   
        try
        {   
            Marshal.StructureToPtr(structObj,buffer,false);   
            byte[]  bytes  =   new byte[size];   
            Marshal.Copy(buffer, bytes,0,size);   
            return   bytes;   
        }   
        finally
        {   
            Marshal.FreeHGlobal(buffer);   
        }   
    }   
    //字节数组转结构体   
    public object BytesToStruct(byte[] bytes,   Type   strcutType)   
    {   
        int size = Marshal.SizeOf(strcutType);   
        IntPtr buffer = Marshal.AllocHGlobal(size);   
        try
        {   
            Marshal.Copy(bytes,0,buffer,size);   
            return  Marshal.PtrToStructure(buffer,   strcutType);   
        }   
        finally
        {   
            Marshal.FreeHGlobal(buffer);   
        }      
       
    }   
       
    private void sendCallback (IAsyncResult asyncSend)   
    {   
       
    }   
       
    //关闭Socket   
    public void Closed()   
    {   
       
        if(clientSocket != null && clientSocket.Connected)   
        {   
            clientSocket.Shutdown(SocketShutdown.Both);   
            clientSocket.Close();   
        }   
        clientSocket = null;   
    }   
       
}

为了与服务端达成默契,判断数据包是否完成。我们需要在数据包中定义包头 ,包头一般是这个数据包的长度,也就是结构体对象的长度。正如代码中我们把两个数据类型 short 和 object 合并成一个新的字节数组。

然后是数据包结构体的定义,需要注意如果你在做IOS和Android的话数据包中不要包含数组,不然在结构体转换byte数组的时候会出错。

Marshal.StructureToPtr () error : Attempting to JIT compile method

JFPackage.cs

using UnityEngine;   
using System.Collections;   
using System.Runtime.InteropServices;   
       
public class JFPackage   
{   
    //结构体序列化   
    [System.Serializable]   
    //4字节对齐 iphone 和 android上可以1字节对齐   
    [StructLayout(LayoutKind.Sequential, Pack = 4)]   
    public struct WorldPackage   
    {   
         public byte mEquipID;   
         public byte mAnimationID;   
         public byte mHP;   
         public short mPosx;   
         public short mPosy;   
         public short mPosz;   
         public short mRosx;   
         public short mRosy;   
         public short mRosz;   
       
         public WorldPackage(short posx,short posy,short posz, short rosx, short rosy, short rosz,byte equipID,byte animationID,byte hp)   
         {   
            mPosx = posx;   
            mPosy = posy;   
            mPosz = posz;   
            mRosx = rosx;   
            mRosy = rosy;   
            mRosz = rosz;   
            mEquipID = equipID;   
            mAnimationID = animationID;   
            mHP = hp;   
         }   
       
    };     
       
}

在脚本中执行发送数据包的动作,在Start方法中得到Socket对象。

public JFSocket mJFsorket;   
       
void Start ()   
{   
    mJFsorket = JFSocket.GetInstance();   
}

让角色发生移动的时候,调用该方法向服务端发送数据。

void SendPlayerWorldMessage()   
{   
                //组成新的结构体对象,包括主角坐标旋转等。   
     Vector3 PlayerTransform = transform.localPosition;   
     Vector3 PlayerRotation = transform.localRotation.eulerAngles;   
                //用short的话是2字节,为了节省包的长度。这里乘以100 避免使用float 4字节。当服务器接受到的时候小数点向前移动两位就是真实的float数据   
     short px =  (short)(PlayerTransform.x*100);   
     short py =  (short)(PlayerTransform.y*100);   
     short pz =  (short)(PlayerTransform.z*100);   
     short rx =  (short)(PlayerRotation.x*100);   
     short ry =  (short)(PlayerRotation.y*100);   
     short rz =  (short)(PlayerRotation.z*100);   
     byte equipID = 1;   
     byte animationID =9;   
     byte hp = 2;   
     JFPackage.WorldPackage wordPackage = new JFPackage.WorldPackage(px,py,pz,rx,ry,rz,equipID,animationID,hp);   
                //通过Socket发送结构体对象   
     mJFsorket.SendMessage(wordPackage);   
}

接着就是客户端同步服务器的数据,目前是测试阶段所以写的比较简陋,不过原理都是一样的。哇咔咔!!

//上次同步时间   
      private float mSynchronous;   
       
void Update ()   
{   
       
    mSynchronous +=Time.deltaTime;   
    //在Update中每0.5s的时候同步一次   
    if(mSynchronous > 0.5f)   
    {   
        int count = mJFsorket.worldpackage.Count;   
        //当接受到的数据包长度大于0 开始同步   
        if(count > 0)   
        {   
                               //遍历数据包中 每个点的坐标   
            foreach(JFPackage.WorldPackage wp in mJFsorket.worldpackage)   
            {   
                float x = (float)(wp.mPosx / 100.0f);   
                float y = (float)(wp.mPosy /100.0f);   
                float z = (float)(wp.mPosz /100.0f);   
       
                Debug.Log("x = " + x + " y = " + y+" z = " + z);   
                  //同步主角的新坐标   
                mPlayer.transform.position = new Vector3 (x,y,z);   
            }   
                               //清空数据包链表   
            mJFsorket.worldpackage.Clear();   
        }   
        mSynchronous = 0;   
    }   
}

主角移动的同时,通过Socket时时同步坐标喔。。有没有感觉这个牛头人非常帅气 哈哈哈。

对于Socket的使用,我相信没有比MSDN更加详细的了。 有关Socket 同步请求异步请求的地方可以参照MSDN  链接地址给出来了,好好学习吧,嘿嘿。 http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.aspx

上述代码中我使用的是Thread() 没有使用协同任务StartCoroutine() ,原因是协同任务必需要继承MonoBehaviour,并且该脚本要绑定在游戏对象身上。问题绑定在游戏对象身上切换场景的时候这个脚本必然会释放,那么Socket肯定会断开连接,所以我需要用Thread,并且协同任务它并不是严格意义上的多线程。

2.HTTP

HTTP请求在Unity我相信用的会更少一些,因为HTTP会比SOCKET慢很多,因为它每次请求完都会断开。废话不说了, 我用HTTP请求制作用户的登录。用HTTP请求直接使用Unity自带的www类就可以,因为HTTP请求只有登录才会有, 所以我就在脚本中来完成, 使用 www 类 和 协同任务StartCoroutine()。

using UnityEngine;   
using System.Collections;   
using System.Collections.Generic;   
public class LoginGlobe : MonoBehaviour {   
       
    void Start ()   
    {   
        //GET请求   
        StartCoroutine(GET("http://xuanyusong.com/"));   
       
    }   
       
    void Update ()   
    {   
       
    }   
       
    void OnGUI()   
    {   
       
    }   
       
    //登录   
    public void LoginPressed()   
    {   
        //登录请求 POST 把参数写在字典用 通过www类来请求   
        Dictionary<string,string> dic = new Dictionary<string, string> ();   
        //参数   
        dic.Add("action","0");   
        dic.Add("usrname","xys");   
        dic.Add("psw","123456");   
       
        StartCoroutine(POST("http://192.168.1.12/login.php",dic));   
       
    }   
    //注册   
    public void SingInPressed()   
    {   
        //注册请求 POST   
        Dictionary<string,string> dic = new Dictionary<string, string> ();   
        dic.Add("action","1");   
        dic.Add("usrname","xys");   
        dic.Add("psw","123456");   
       
        StartCoroutine(POST("http://192.168.1.12/login.php",dic));   
    }   
       
    //POST请求   
    IEnumerator POST(string url, Dictionary<string,string> post)   
    {   
        WWWForm form = new WWWForm();   
        foreach(KeyValuePair<string,string> post_arg in post)   
        {   
            form.AddField(post_arg.Key, post_arg.Value);   
        }   
       
        WWW www = new WWW(url, form);   
        yield return www;   
       
        if (www.error != null)   
        {   
            //POST请求失败   
            Debug.Log("error is :"+ www.error);   
       
        } else
        {   
            //POST请求成功   
             Debug.Log("request ok : " + www.text);   
        }   
    }   
       
    //GET请求   
    IEnumerator GET(string url)   
    {   
       
        WWW www = new WWW (url);   
        yield return www;   
       
        if (www.error != null)   
        {   
            //GET请求失败   
            Debug.Log("error is :"+ www.error);   
       
        } else
        {   
            //GET请求成功   
             Debug.Log("request ok : " + www.text);   
        }   
    }   
       
}

如果想通过HTTP传递二进制流的话 可以使用 下面的方法。

WWWForm wwwForm = new WWWForm();   
       
     byte[] byteStream = System.Text.Encoding.Default.GetBytes(stream);   
       
     wwwForm.AddBinaryData("post", byteStream);   
       
     www = new WWW(Address, wwwForm);

目前Socket数据包还是没有进行加密算法,后期我会补上。欢迎讨论,互相学习互相进度 加油,蛤蛤。

下载地址我不贴了,因为没有服务端的东西 运行也看不到效果。 希望大家学习愉快, 我们下次再见!

 

2017-08-14 17:50:55 gsm958708323 阅读数 386
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4641 人正在学习 去看看 张刚

暑假在校的最后一天,日期2017.8.14,总结一下上次作品的收获。
放图,hiahiahia~
AR说明书登陆窗口
Ps:背景没有换,因为在诸多背景中还是挑选的原生。

实现内容:实现账号的正常登录,扫描3D物体出现弹幕评价信息,登录即可发送弹幕,这是我首次尝试用unity与后台进行通信,虽然功能不算很强大,但是在unity方面又学到不少新的知识!!!

首先做的是发送界面,第一步在客户端实现弹幕的发送。

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

public class TextItem : MonoBehaviour {

    public string text = "";//控件显示文字
    private Text currentText;//当前脚本所在的text控件
    public float speed;//弹幕移动的速度


    // Use this for initialization
    void Start () {
        currentText = GetComponent<Text>();//初始化

        //设置随机的字体及颜色
        currentText.text = text;
        currentText.color = Random.ColorHSV();

        //获取屏幕范围内的y随机坐标,这里没有做屏幕适配,free aspect举例
        float y = Random.Range(-200f, 200f);
        transform.localPosition = new Vector3(550f, y, 0);

    }

    // Update is called once per frame
    void Update () {
        if (speed != 0) {
            float x = transform.localPosition.x + speed * Time.deltaTime;
            transform.localPosition = new Vector3(x, transform.localPosition.y, 1);

            //超出屏幕销毁
            //if (transform.localPosition .x<-550f) {
            //    Destroy(gameObject);
            //}
            Destroy(this.gameObject, 20f);
        }
    }
}

当然这个东西一定要放在Text文字物体上啊,然后做成Prefab啊,以便一会发弹幕的时候调用啊,就像这样子啊~~~~
弹幕预制体

接下来先制作发送界面,这个简答了,直接上UI。
这里写图片描述
啊哈哈哈,是不是很简单,主要是代码啊啊啊啊~~~

下面来研究一下与后台通信的代码。我把他做成了一个带构造的实体类,方面传值和重复调用。

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using UnityEngine;
/// <summary>
/// 创建一个能够联网的实体类
/// </summary>
public class PostHttp
{
    public string responseContent;

    public PostHttp(string url, string body, string contentType)
    {
        HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);

        httpWebRequest.ContentType = contentType;
        httpWebRequest.Method = "POST";
        httpWebRequest.Timeout = 5000;

        byte[] btBodys = Encoding.UTF8.GetBytes(body);
        httpWebRequest.ContentLength = btBodys.Length;
        httpWebRequest.GetRequestStream().Write(btBodys, 0, btBodys.Length);

        HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
        StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream());
        string responseContent = streamReader.ReadToEnd();

        httpWebResponse.Close();
        streamReader.Close();
        httpWebRequest.Abort();
        httpWebResponse.Close();

        //return responseContent;
        this.responseContent = responseContent;
    }
}

也是不大清楚这里写的什么意思,大概直接调用后台就会返回一个String类型的字符串,里面好像包含了uid,id,弹幕内容,大概就这些,嗯,emmmm。

按钮上面挂的脚本就是这样的。

    //发送弹幕按钮
    public void MoveTextSendButton()
    {
        if (LoginManager.LoginID != null)
        {
            Debug.LogWarning("此时的ID为"+LoginManager.LoginID);
            MoveTextID = LoginManager.LoginID;
            PostHttp posthttp = new PostHttp("http://123.207.38.205:8080/Instruction/SendServlet", "id=" + MoveTextID + "&danmu=" + moveTextStr, "application/x-www-form-urlencoded");
        }
        else
        {
            Debug.LogWarning("此时的ID为Null" + LoginManager.LoginID);
            StartCoroutine(Load());
        }
    }

    IEnumerator Load()
    {
        sendMessageText.text = "您还没有登录,即将跳转到登录页面.....";
        yield return new WaitForSeconds(1f);
        SceneManager.LoadScene("Login");
    }

但new出上次创建的PostHttp的实体,就可以把输入的内容发送到后台喽,是不是炒鸡简单。回头看一看刚才的PostHttp类,public PostHttp(string url, string body, string contentType),里面需要传入三个参数,这是后台给的,听后台老大的话准没错!

登录:
ip:8080/Instruction/LoginServlet
发送参数:username    password 
返回结果  true+用户id/false
传递方式:post

发送弹幕:
ip:8080/Instruction/SendServlet
发送参数:id(用户id) danmu
返回结果: true/false
传递方式:post


弹幕显示:
ip:8080/Instruction/DisplayServlet
发送参数:上次发送弹幕的最后一个id
返回结果:id(弹幕id) message(弹幕具体内容) uid(用户id)
传递方式:post 

需要什么功能传一些对应的数值就好了,上面只是示例,千万不要直接照搬我的啊,555555
今天还有些事情,有时间在更啦~


2017-06-18 10:25:20 tropicofcancer9 阅读数 2644
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4641 人正在学习 去看看 张刚

http://www.cnblogs.com/wanglufly/p/4086788.html


首先保证项目在Unity上运行正常,有时候电脑上运行成功也不代表手机上能运行成功,总之会出现各种问题,还是需要丰富的经验。

简单来说,步骤如下:Unity打包IOS---生成XCode项目---配置开发者账号---安装开发者证书(如果发布的话需要发布证书)---XCode在真机上运行---生成ipa

在unity Build Settings里面选择平台为Platform为ios,再在Player Setting中配置一下,如果你打包过安卓,对此应该就很熟悉了,配置基本差不多,需要注意的一点就是要Other Settings中配置一下Bundle Identifier,这个以后也会在苹果的开发者网站填写。

 

好了,导出的项目可以用XCode打开了,我推荐使用XCode5,因为XCode6打包的时候出现了问题真的是很莫名其妙。

再来说说这一系列繁琐的证书过程,首先要看看你这台电脑是不是当初申请开发者账号的那台,如果是那自然万事大吉,如果不是,则要从申请开发者账号的那台机器导出一个以p12为后缀的文件,打开钥匙串访问,选择那个证书,右键“导出XXX”,选择导出位置,文件格式选择最后一项“个人信息交换(p12)”,然后拷贝到你的机器,双击添加到钥匙串即可,如果申请开发者账号的那台机器暂时不可用,则需要重新在苹果官网申请,“钥匙串访问”-“证书助理”-“从证书颁发机构请求证书”  

 

输入你的电子邮件地址和常用名称,然后选择第二项“存储到磁盘”,点击“继续”

 

 

最后会在桌面生成这样一个Request文件,然后登录到苹果的开发者网站https://developer.apple.com/,选择“IOS Apps”,然后右上角证书管理,然后再左边IOS Apps,第一项“Certificates”,如果想要在真机上调试,需要注册设备,点击“Devices”,右上角点击“+”,填写设备的名称和唯一的标示符,标示符有很多办法可以获取到,例如插上你的设备,在XCode中可以看到一串标识码

 

接下来需要对你的App进行注册,点击“Identifiers”里的“App IDs”,点击“+”:

 

其中“Name”是App的名字,“Bundle ID”最好和Unity里的Bundle ID填的一样,接下来是“Provisioning Profiles”,如果是开发就选择“Development”并添加一个,如果是发布就选择“Distribution”并添加一个,

 

填写上基本的发布信息之后就可以把该发布证书Download到电脑上,再选择“Certificates”,同样的,发布选“Distribution”,开发选“Development”,等待这一系列步骤操作完成之后,桌面上会生成如下四个文件,两两对应,双击把它们添加到钥匙串访问里面:

 

最后回到XCode里面,选择“Product”里面的“Archive”:

 

漫长的编译之后出现如下页面,左侧空白为应用图标,点击“Validate”:

 

图1

提示登录开发者账号,账号密码会自动填充:

 

如果“Provisioning Profile”处出现黄色警告,则要检查一下钥匙串里是否将该应用的发布证书添加进去了,并且保证你选择的证书是否跟你这个Application匹配:

 

出现这个页面就表示验证将要成功了:

验证完成之后要导出ipa,选择图1 的“Distribute”,选择第二项“Save for Enterprise or Ad Hoc Deployment”:

 

然后选择正确的发布证书,点击“Export”,出现如下界面,就表示将要大功告成了:

 

最后会在桌面上看到一个ipa文件就是最终的包了。

没有更多推荐了,返回首页