最近在做一款打星球2D游戏,大概玩法就是屏幕中间有个星球(可能是一张图片,也可能是几张图片组合),导弹从星球四面八方朝星球攻击,星球收到攻击后会爆炸掉一块区域。
QQ录屏20220513142803.gif
如果单单一张图很好处理,这里讲的是如何处理多张图片的问题。
我当时考虑到三种方案:
1.获取多张图像素信息,依次遍历图片绘制到一张图片上;
2.直接以多张图片的渲染在屏幕中,导弹打击后遍历多张图片,计算缺口;
3.多张图片渲染后截取当前区域的图片,用截图后的图片替换之前的多张图片,导弹打击后只用计算这一张图片的缺口。
方案一
- 获取多张图像素信息,依次遍历图片绘制到一张图片上;
图片大小一比一的绘制到一张图片上
思路:通过中心点和图片每个像素的颜色信息(color)计算出当前像素在一张大图上的位置信息(x,y);
为了方便计算,我定义一张最大图为512*512(最下面一张图片的大小),然后把所有图片的像素信息一次绘制到这张大图上(从最下面一张图片依次向上面图片遍历),假设所有图片中心位置相等。
public Texture2D MergeImage(Texture2D[] tex)
{
if (tex.Length == 0)
{
return null;
}
//定义新图的宽高, 合并分为两种情况水平方向合并、垂直方向合并
int width = 512, height = 512;
//初始Texture2D
Texture2D texture2D = new Texture2D(width, height);
int x = 0, y = 0;
for (int i = 0; i < tex.Length; i++)
{
Texture2D tex= tex[i];
for(int m =0;m<tex.width;m++)
{
for(int n =0;n<tex.height;n++)
{
Color color = tex.GetPixel(m,n);
if(i==0)
texture2D.SetPixel(m,n) = color;
else
{
if(color.a ==0) continue;
texture2D.SetPixel((width - tex.width)/2+m,(height - tex.height)/2 + n) = color;
}
}
}
}
//应用
texture2D.Apply();
return texture2D;
}
图片缩放一定比例绘制到一张图片上
private void DrawSprite()
{
string cellInfo = _map.cellInfo;
string[] cells = cellInfo.Split(';');
for(int i = 0; i < cells.Length; i++)
{
string[] cell = cells[i].Split('-');
Texture2D sprite = AssetLoader.Load<Texture2D>($"Textures/cell/{cell[0]}");
float scaleMul = float.Parse(cell[1]);
Color hexColor = Utils.HexToColor(cell[2]);
int currentTexWidth = (int)(sprite.width * scaleMul); //当前图放大一定倍数后宽度
int currentTexHeight = (int)(sprite.height * scaleMul); //当前图放大一定倍数后高度
if (i == 0)
{
_mWidth = currentTexWidth;
_mHeight = currentTexHeight;
_tex = new Texture2D(_mWidth, _mHeight);
//_tex.SetPixels(sprite.GetPixels());
_cellSp.sprite = Sprite.Create(_tex, new Rect(0, 0, _tex.width, _tex.height), Vector2.one * 0.5f);
}
int leftBotX = (_mWidth - currentTexWidth) / 2;//当前图片在最下面一张图的左下x位置
int leftBotY = (_mHeight - currentTexHeight) / 2; //当前图片在最下面一张图的左下y位置
for (int x = leftBotX; x < leftBotX + currentTexWidth; x++)
{
for(int y = leftBotY; y < leftBotY + currentTexHeight; y++)
{
int xnverseX = x - leftBotX;
int xnverseY = y - leftBotY;
Color color = sprite.GetPixel(Mathf.FloorToInt(xnverseX / scaleMul), Mathf.FloorToInt(xnverseY / scaleMul));
color = GetTexPixel(xnverseX,xnverseY,scaleMul,sprite);
if (i == 0)
{
if (cell[2].Equals("1"))
_tex.SetPixel(x, y, color);
else
{
if (color.a == 0)
_tex.SetPixel(x, y, color);
else
{
//hexColor.a = color.a;
_tex.SetPixel(x, y, hexColor * color);
}
}
}
else
{
if (color.a != 0)
{
if (cell[2].Equals("1"))
_tex.SetPixel(x, y, color);
else
{
//hexColor.a = color.a;
_tex.SetPixel(x, y, hexColor * color);
}
}
}
}
}
}
//TexLerp();
_tex.Apply();
}
这里就有个问题,缩放一定比例,按比例绘制到大图上,边缘就会出现大量锯齿;我猜想我的算法过于简单,也没有按像素权重算法去计算,官方对弈缩放图片应该有他们一套算法(类似于抗锯齿算法解决大量锯齿原因)。
- 我想要解决这个问题首先要了解官方对于图片缩放像素的计算问题,如果有了解或者能解决这个问题的大神欢迎在下面评论,说出您的算法。
方案二
- 直接以多张图片的渲染在屏幕中,导弹打击后遍历多张图片,计算缺口;
这种方案无疑是能达到游戏设计的最终目的,但是计算量太大了,需要每次计算缺口的时候变量所有图片,计算出每张图片丢失的像素点。而且处理不好,两张重叠图片缺口处,缺口不一定整齐。最终放弃掉。
方案三
- 多张图片渲染后截取当前区域的图片,用截图后的图片替换之前的多张图片,导弹打击后只用计算这一张图片的缺口。
从屏幕中截取部分图片
IEnumerator CaptureScreenshot()
{
yield return new WaitForEndOfFrame();
CellModel model = MapCtrl.Instance.GetCellModel(0);
float originalWidth = model.m_Width * model.m_scaleMul;
float originalHeight = model.m_Height * model.m_scaleMul;
float twidth = originalWidth * FixScreen.unit;
float theight = originalHeight * FixScreen.unit;
Vector3 leftDownPos = MapCtrl.Instance.CenterPos + new Vector3(-twidth * 0.5f, -theight * 0.5f, 0);
Vector3 rightTopPos = MapCtrl.Instance.CenterPos + new Vector3(twidth * 0.5f, theight * 0.5f, 0);
Vector3 leftDownscreenPos = Camera.main.WorldToScreenPoint(leftDownPos);
Vector3 rightTopscreenPos = Camera.main.WorldToScreenPoint(rightTopPos);
twidth = rightTopscreenPos.x - leftDownscreenPos.x;
theight = rightTopscreenPos.y - leftDownscreenPos.y;
Rect rect = new Rect(leftDownscreenPos.x, leftDownscreenPos.y, twidth, theight);
Texture2D screenShot = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGBA32, false);
screenShot.ReadPixels(rect, 0, 0);
screenShot.Apply();
}
这里会出现另外一个问题:截取的制定区域如果有其他元素(比如UI),会把不相关元素一起截图进来,最终放弃。
通过指定相机RenderTexture截取指定图片
先把需要加载的渲染出来,然后再用Camera RenderTexture 解决指定区域图片替换之前渲染出来的图片
注意由于设备渲染纹理的坐标差异,需要判断下原点是否是在左上角还是左下角,这样有利于世界坐标转换
Unity为了兼容D3D,OpenGLES,Metal等不同3D API的特性,内部做了适配,让开发者不同去考虑这些差异。Unity默认是以OpenGL的标准体系进行描述的:左手坐标系、屏幕坐标系左下角为(0,0)等。为了确保统一性,所有 non-OpenGL的平台的特性,Unity会做出转换,使得该特性能够以OpenGL的标准来描述。(现在有点明白Unity是怎么做到跨平台开发的了)
首先引发问题的根本原因在于RenderTexture坐标系的差异:
OpenGL是从左下角为(0,0)开始,往上递增。
D3D从左上角为(0,0)开始,往下递增。
image.png
CellModel model = MapCtrl.Instance.GetCellModel(0);
float originalWidth = model.m_Width * model.m_scaleMul;
float originalHeight = model.m_Height * model.m_scaleMul;
float twidth = originalWidth * FixScreen.unit;
float theight = originalHeight * FixScreen.unit;
Rect rect;
//if (SystemInfo.graphicsDeviceVersion.StartsWith("OpenGL"))
//注意由于设备渲染纹理的坐标差异,判断下原点是否是在左上角还是左下角
if(SystemInfo.graphicsUVStartsAtTop)
{
//左上角
Vector3 leftTopPos = MapCtrl.Instance.CenterPos + new Vector3(-twidth * 0.5f, theight * 0.5f, 0); //左上角
Vector3 rightDownPos = MapCtrl.Instance.CenterPos + new Vector3(twidth * 0.5f, -theight * 0.5f, 0); //右下角
Vector3 leftTopscreenPos = Camera.main.WorldToScreenPoint(leftTopPos);
Vector3 rightDownscreenPos = Camera.main.WorldToScreenPoint(rightDownPos);
twidth = rightDownscreenPos.x - leftTopscreenPos.x;
theight = leftTopscreenPos.y - rightDownscreenPos.y;
rect = new Rect((int)leftTopscreenPos.x, (int)(Screen.height - leftTopscreenPos.y), (int)twidth, (int)theight);
}
else
{
//左下角
Vector3 leftDownPos = MapCtrl.Instance.CenterPos + new Vector3(-twidth * 0.5f, -theight * 0.5f, 0);
Vector3 rightTopPos = MapCtrl.Instance.CenterPos + new Vector3(twidth * 0.5f, theight * 0.5f, 0);
Vector3 leftDownscreenPos = Camera.main.WorldToScreenPoint(leftDownPos);
Vector3 rightTopscreenPos = Camera.main.WorldToScreenPoint(rightTopPos);
twidth = rightTopscreenPos.x - leftDownscreenPos.x;
theight = rightTopscreenPos.y - leftDownscreenPos.y;
rect = new Rect((int)leftDownscreenPos.x, (int)leftDownscreenPos.y, (int)twidth, (int)theight);
}
RenderTexture rt = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
Camera.main.targetTexture = rt;
Camera.main.Render();
yield return new WaitForEndOfFrame();
RenderTexture.active = rt;
Texture2D t = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.ARGB32, false);
t.ReadPixels(rect, 0, 0);
t.Apply();
Camera.main.targetTexture = null;
RenderTexture.active = null;
个人认为如果能用图片缩放一定比例绘制到一张图片上最终实现游戏效果是比较好的,欢迎大家留言。
网友评论