手把手带你使用Paint in 3D和Photon撸一个在线涂鸦画板
Paint in 3D
Paint in 3D用于在游戏内和编辑器里绘制所有物体。所有功能已经过深度优化,在WebGL、移动端、VR 以及更多平台用起来都非常好用!
它支持标准管线,以及 LWRP、HDRP 和 URP。通过使用GPU 加速,你的物体将以难以置信的速度被绘制。代码还经过深度优化来防止GC,和将所有绘制操作一起批次完成。
跟贴图系统不同,它是一个纹理绘制解决方案。这意味着你可以绘制你的物体上百万次,还是无帧率丢失,让你创作难以想象的游戏。
它在Unity应用商店上的售价是60美元,地址:https://assetstore.unity.com/packages/tools/painting/paint-in-3d-26286。
Photon
Photon中文翻译为“光子”,为有着15年服务器后端开发经验的德国Exit Games公司开发的高效,稳定,可拓展的网络引擎。为目前世界上用户最广泛,支持游戏类型最多的专业网络引擎之一,也是Unity应用商店里用户评价最高的网络组件。
世界多个知名游戏公司和工作室选用Photon作为其产品的网络支持引擎,其中包括WB华纳,Codemaster, 2K, Glu, 微软游戏工作室,史克威尔艾尼克斯,百代南梦宫,SandBox,雨神电竞等知名企业,也有许多工作室和新创企业正在了解和试用Photon之中。
它在Unity应用商店上有一个免费应用,地址:https://assetstore.unity.com/packages/tools/network/pun-2-free-119922。
当然,Photon需要注册账号、创建应用等操作才能使用,还不了解的同学可以去官方网站查阅相关资料。
温馨提示:Photon的国外服务器在国内使用比较卡,所以最好去中国官网申请国内的服务器,申请地址:https://vibrantlink.com/chinacloudapply/。
下面正式开始。

创建工程
使用Unity Hub创建一个3D项目,然后分别引入Paint in 3D和Photon Unity Networking 2,如下图:
温馨提示:在引入Photon Unity Networking 2后,记得配置AppId。
创建简易画板
为了方便演示,我们创建一个Quad作为画板,然后为其添加P3dPaintable、P3dMaterialCloner和P3dPaintableTexture组件,使用它们的默认配置即可,如下图:
然后,创建一个空的GameObject命名为OneMorePaint
,然后向OneMorePaint
添加P3dPaintSphere组件,修改P3dPaintSphere组件的Color为红色,其他配置保持默认不变,如下图:
再向OneMorePaint
添加P3dHitScreen组件,勾选上P3dHitScreen组件的ConnectHits,其他配置保持默认不变,如下图:
这时候,创建简易画板就做好了,运行以后就可以画画了,如下图:
只不过,还是个单机版,我们加上实时在线功能。

连接PUN2服务器
创建一个C#脚本命名为Launcher
,再创建一个空的GameObject命名为LauncherGameObject
,把C#脚本Launcher
添加到LauncherGameObject
中。
编辑C#脚本Launcher
为如下内容:
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
namespace One.More
{
public class Launcher : MonoBehaviourPunCallbacks
{
#region Private Fields
/// <summary>
/// This client's version number. Users are separated from each other by gameVersion (which allows you to make breaking changes).
/// </summary>
string gameVersion = "1";
/// <summary>
/// Keep track of the current process. Since connection is asynchronous and is based on several callbacks from Photon,
/// we need to keep track of this to properly adjust the behavior when we receive call back by Photon.
/// Typically this is used for the OnConnectedToMaster() callback.
/// </summary>
bool isConnecting;
#endregion
void Start()
{
this.Connect();
}
#region MonoBehaviourPunCallbacks Callbacks
public override void OnConnectedToMaster()
{
Debug.Log("PUN Basics Tutorial/Launcher: OnConnectedToMaster() was called by PUN");
// we don't want to do anything if we are not attempting to join a room.
// this case where isConnecting is false is typically when you lost or quit the game, when this level is loaded, OnConnectedToMaster will be called, in that case
// we don't want to do anything.
if (isConnecting)
{
// #Critical: The first we try to do is to join a potential existing room. If there is, good, else, we'll be called back with OnJoinRandomFailed()
PhotonNetwork.JoinRandomRoom();
isConnecting = false;
}
}
public override void OnDisconnected(DisconnectCause cause)
{
Debug.LogWarningFormat("PUN Basics Tutorial/Launcher: OnDisconnected() was called by PUN with reason {0}", cause);
isConnecting = false;
}
public override void OnJoinRandomFailed(short returnCode, string message)
{
Debug.Log("PUN Basics Tutorial/Launcher:OnJoinRandomFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom");
// #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
PhotonNetwork.CreateRoom(null, new RoomOptions());
}
public override void OnJoinedRoom()
{
Debug.Log("PUN Basics Tutorial/Launcher: OnJoinedRoom() called by PUN. Now this client is in a room.");
}
#endregion
#region Public Methods
/// <summary>
/// Start the connection process.
/// - If already connected, we attempt joining a random room
/// - if not yet connected, Connect this application instance to Photon Cloud Network
/// </summary>
public void Connect()
{
// we check if we are connected or not, we join if we are , else we initiate the connection to the server.
if (PhotonNetwork.IsConnected)
{
// #Critical we need at this point to attempt joining a Random Room. If it fails, we'll get notified in OnJoinRandomFailed() and we'll create one.
PhotonNetwork.JoinRandomRoom();
}
else
{
// #Critical, we must first and foremost connect to Photon Online Server.
isConnecting = PhotonNetwork.ConnectUsingSettings();
PhotonNetwork.GameVersion = gameVersion;
}
}
#endregion
}
}
这时候,就可以连接到连接PUN2服务器了,运行以后我们可以看到如下日志:

实时在线同步
向之前创建的OneMorePaint
添加PhotonView组件,使用默认配置即可,如下图:
创建一个C#脚本命名为OnlinePainting
,把C#脚本OnlinePainting
添加到OneMorePaint
中。
编辑C#脚本OnlinePainting
为如下内容:
using PaintIn3D;
using Photon.Pun;
using UnityEngine;
namespace One.More
{
public class OnlinePainting : MonoBehaviour, IHitPoint, IHitLine
{
private PhotonView photonView;
private P3dPaintSphere paintSphere;
void Start()
{
this.photonView = this.GetComponent<PhotonView>();
this.paintSphere = this.GetComponent<P3dPaintSphere>();
}
public void HandleHitPoint(bool preview, int priority, float pressure, int seed, Vector3 position, Quaternion rotation)
{
if (preview)
{
return;
}
if (this.photonView == null)
{
Debug.LogError("PhotonView is not found.");
return;
}
this.photonView.RPC("HandleHitPointRpc", RpcTarget.Others, preview, priority, pressure, seed, position, rotation);
}
public void HandleHitLine(bool preview, int priority, float pressure, int seed, Vector3 position, Vector3 endPosition, Quaternion rotation, bool clip)
{
if (preview)
{
return;
}
if (this.photonView == null)
{
Debug.LogError("PhotonView is not found.");
return;
}
this.photonView.RPC("HandleHitLinetRpc", RpcTarget.Others, preview, priority, pressure, seed, position, endPosition, rotation, clip);
}
[PunRPC]
public void HandleHitPointRpc(bool preview, int priority, float pressure, int seed, Vector3 position, Quaternion rotation)
{
if (this.paintSphere == null)
{
Debug.LogError("P3dPaintSphere is not found.");
return;
}
this.paintSphere.HandleHitPoint(preview, priority, pressure, seed, position, rotation);
}
[PunRPC]
public void HandleHitLinetRpc(bool preview, int priority, float pressure, int seed, Vector3 position, Vector3 endPosition, Quaternion rotation, bool clip)
{
if (this.paintSphere == null)
{
Debug.LogError("P3dPaintSphere is not found.");
return;
}
this.paintSphere.HandleHitLine(preview, priority, pressure, seed, position, endPosition, rotation, clip);
}
}
}
在线涂鸦画板就制作完成了,我们看看运行效果怎么样?
运行效果
构建以后,同时启动两个客户端,效果如下:
当然,这只是简单的在线涂鸦画板,你还可以再此基础上添加更丰富的功能,比如:修改画笔颜色、修改画笔大小等等。

最后,谢谢你这么帅,还给我点赞和关注。
手把手带你使用Paint in 3D和Photon撸一个在线涂鸦画板的更多相关文章
- 手把手带你做一个超炫酷loading成功动画view Android自定义view
写在前面: 本篇可能是手把手自定义view系列最后一篇了,实际上我也是一周前才开始真正接触自定义view,通过这一周的练习,基本上已经熟练自定义view,能够应对一般的view需要,那么就以本篇来结尾 ...
- 手把手教你玩转 CSS3 3D 技术
css3的3d起步 要玩转css3的3d,就必须了解几个词汇,便是透视(perspective).旋转(rotate)和移动(translate).透视即是以现实的视角来看屏幕上的2D事物,从而展现3 ...
- 手把手教你玩转CSS3 3D技术
手把手教你玩转 CSS3 3D 技术 要玩转css3的3d,就必须了解几个词汇,便是透视(perspective).旋转(rotate)和移动(translate).透视即是以现实的视角来看屏幕上 ...
- [.Net] 手把手带你将自己打造的类库丢到 NuGet 上
手把手带你将自己打造的类库丢到 NuGet 上 序 我们习惯了对项目右键点击“引用”,选择“管理NuGet 程序包”来下载第三方的类库,可曾想过有一天将自己的打造的类库放到 NuGet 上,让第三者下 ...
- Android性能优化:手把手带你全面实现内存优化
前言 在 Android开发中,性能优化策略十分重要 本文主要讲解性能优化中的内存优化,希望你们会喜欢 目录 1. 定义 优化处理 应用程序的内存使用.空间占用 2. 作用 避免因不正确使用内 ...
- Android:手把手带你深入剖析 Retrofit 2.0 源码
前言 在Andrroid开发中,网络请求十分常用 而在Android网络请求库中,Retrofit是当下最热的一个网络请求库 今天,我将手把手带你深入剖析Retrofit v2.0的源码,希望你们会喜 ...
- [转帖]从零开始入门 K8s | 手把手带你理解 etcd
从零开始入门 K8s | 手把手带你理解 etcd https://zhuanlan.zhihu.com/p/96721097 导读:etcd 是用于共享配置和服务发现的分布式.一致性的 KV 存储系 ...
- 手把手带你阅读Mybatis源码(三)缓存篇
前言 大家好,这一篇文章是MyBatis系列的最后一篇文章,前面两篇文章:手把手带你阅读Mybatis源码(一)构造篇 和 手把手带你阅读Mybatis源码(二)执行篇,主要说明了MyBatis是如何 ...
- GitHub 热点速览 Vol.26:手把手带你做数据库
作者:HelloGitHub-小鱼干 摘要:手把手带你学知识,应该是学习新知识最友好的姿势了.toyDB 虽然作为一个"玩具"项目不能应用在实际开发中,但通过它你可以了解到如何制作 ...
随机推荐
- Java在算法题中的输入问题
Java在算法题中的输入问题 在写算法题的时候,经常因为数据的输入问题而导致卡壳,其中最常见的就是数据输入无法结束. 1.给定范围,确定输入几个数据 直接使用普通的Scanner输入数据范围,然后使用 ...
- suse 12 二进制部署 Kubernetets 1.19.7 - 第11章 - 部署coredns组件
文章目录 1.11.0.部署coredns 1.11.1.测试coredns功能 suse 12 二进制部署 Kubernetes 集群系列合集: suse 12 二进制部署 Kubernetets ...
- [自动化]ansible-系统安全加固整改
基线漏洞安全整改 修复环境:centos7及以上 安全基线的概念 安全基线是一个信息系统的最小安全保证,即该信息系统最基本需要满足的安全要求.信息 系统安全往往需要在安全付出成本与所能够承受的安全风险 ...
- RFC2899广播吞吐量测试——网络测试仪实操
一.简介 RFC 2889为LAN交换设备的基准测试提供了方法学,它将RFC 2544中为网络互联设备基准测试所定义的方法学扩展到了交换设备,提供了交换机转发性能(Forwarding Perform ...
- 程序与CPU,内核,寄存器,缓存,RAM,ROM、总线、Cache line缓存行的作用和他们之间的联系?
目录 缓存 什么是缓存 L1.L2.L3 为什么要设置那么多缓存.缓存在cup内还是cup外 MESI协议----主流的处理缓存和主存数据不一样问题 Cache line是什么已经 对编程中数组的影响 ...
- 【C#基础概念】命名规范
1. 引言 本文是一套面向C# programmer 和C# developer 进行开发所应遵循的开发规范. 按照此规范来开发C#程序可带来以下益处: · 代码的编写保持一致性, · ...
- k8s 中 nfs作为存储的三种方式
1.安装nfs服务.直接给命令 yum install nfs-utils vim /etc/exports /data/k8s/ 172.16.1.0/24(sync,rw,no_root_squa ...
- One-Hot编码(转)
机器学习:数据预处理之独热编码(One-Hot) 前言 ---------------------------------------- 在机器学习算法中,我们经常会遇到分类特征,例如:人的性别有男女 ...
- JZ-012-数值的整数次方
数值的整数次方 题目描述 给定一个double类型的浮点数base和int类型的整数exponent.求base的exponent次方. 保证base和exponent不同时为0. 题目链接: 数值的 ...
- 《手把手教你》系列基础篇(七十六)-java+ selenium自动化测试-框架设计基础-TestNG实现DDT - 下篇(详解教程)
1.简介 今天这一篇宏哥主要是结合实际工作中将遇到的测试场景和前边两篇学习的知识结合起来给大家讲解和分享一下,希望以后大家在以后遇到其他的测试场景也可以将自己的所学的知识应用到测试场景中. 2.测试场 ...