1、享元模式:
多个指针指向一个类的一个实例,减少内存开销,个人认为这个模式更适合用在某个类的实例所需内存远大于一个指针所占内存且需要大批量生成某物体的时候
2、使用工具:
Unity2022.3.10f1c1、visualstudio2019
3、代码及思路:
延伸到unity的生成地形的话,考虑tile使用基础cube,n种cube的状态只由n个实例来记录,而不是n个cube生成n个实例记录n次状态。
另外,C#本身是为了解决C/C++中指针相关的问题而设计的,因此我们不应该在代码里使用指针。
结合我自认为的享元模式节省内存的重点,我选择在tile上挂一个所需内存小于记录状态类的类,让它和指针一样起到间接访问状态类的作用就可以啦。至于怎么辨认是什么种类的tile,用unity的tag就行。
1)Terrain类
注意unity自己有Terrain类,因此我们需要新建一个namespace(命名空间)来写我们自己的Terrain类
namespace MyWorld
{
public class Terrain//防止和unity的类名字重复
{
readonly int movementCost;
readonly bool isWater;
readonly Material material;
public Terrain(int movementCost, bool isWater, Material material)//构造函数
{
this.movementCost = movementCost;
this.isWater = isWater;
this.material = material;
}
public int GetMoveCost()
{ return movementCost; }
public bool IsWater()
{
return isWater;
}
public Material GetMaterial()
{
return material;
}
}
}
2)WorldManager类
我这里由于只做生成地形的功能因此直接将前面的MyWorld和WorldManager类写在一个脚本里了。它创建了一个矩形的地形网格,每个地形块(tile)可以是不同类型的地形,并根据其类型应用不同的材质。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MyWorld
{
public class Terrain//防止和unity的类名字重复
{
readonly int movementCost;
readonly bool isWater;
readonly Material material;
public Terrain(int movementCost, bool isWater, Material material)//构造函数
{
this.movementCost = movementCost;
this.isWater = isWater;
this.material = material;
}
public int GetMoveCost()
{ return movementCost; }
public bool IsWater()
{
return isWater;
}
public Material GetMaterial()
{
return material;
}
}
}
public class WorldManager : MonoBehaviour
{
public static WorldManager Instance;//单例模式
public Material grass, hill, river;//三种tile的材质
public int witdh,height;//生成一个矩形地形
Tile[,] tiles;//C#里的多维数组
MyWorld.Terrain grassTerrain;
MyWorld.Terrain hillTerrain;
MyWorld.Terrain riverTerrain;
void Start()
{
Instance = this;
//实例化三种地形状态
grassTerrain = new MyWorld.Terrain(1, false, grass);
hillTerrain = new MyWorld.Terrain(3, false, hill);
riverTerrain = new MyWorld.Terrain(2, true, river);
//初始化二维数组
tiles = new Tile[witdh,height];
GenerateTerrain();//生成地形
InitTileMaterial();//初始化tile材质
}
public void GenerateTerrain()//和书上生成地形的逻辑基本一致,只是添加了实物的生成
{
for(int x = 0; x < witdh; x++)
{
for(int y = 0;y < height; y++)
{
//生成方块物体
GameObject tile=GameObject.CreatePrimitive(PrimitiveType.Cube);
tile.transform.position = new Vector3(x, 0, y);
//添加并记录tile
tiles[x, y] = tile.AddComponent<Tile>();
//散落一些山
if (Random.Range(0, 10) == 0)
{
tile.tag = "Hill";
}
else
{
tile.tag = "Grass";//大部分是草
}
}
}
//部分创建好的方块变成一条河
int z = Random.Range(0, witdh);
for(int y=0;y<height;y++)
{
tiles[z, y].tag = "River";
}
}
public MyWorld.Terrain GetType(Tile tile)//实现tile到Terrain的映射
{
return tile.tag switch
{
"Hill" => hillTerrain,
"Grass" => grassTerrain,
"River" => riverTerrain,
_ => null,
};//模式匹配
}
public void InitTileMaterial()
{
foreach(Tile tile in tiles)
{
tile.GetComponent<MeshRenderer>().material = GetType(tile).GetMaterial();//读取terrain里的材质安到cube上
}
}
}
- 将WorldManager做成单例模式的原因是方便其它类对它的直接访问,集中管理游戏中的全局状态,例如地形信息。
- 在Start方法中,实例化了几种预定义的Terrain对象,并将这些对象与相应的材质关联。
- 在GenerateTerrain方法中实现地形生成,tiles二维数组存储所有生成的tile对象,每个tile代表一个地形块。
- 在GetType方法中,接受一个Tile对象作为参数,根据生成地形时设置的tag返回与其对应的Terrain对象(这个方法实现享元)。
- 在InitTileMaterial方法中,遍历tiles数组的每一个tile对象,用GetType方法获得的Terrain的材质属性来设置每个方块的材质
3)Player类
这个就是比较简单的一个控制胶囊体移动,挂在胶囊体上就行了
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(Collider))]
public class Player : MonoBehaviour
{
public float moveSpeed=1.0f;
private void OnValidate()//强制设定值保证player能和cube进行碰撞检测
{
GetComponent<Rigidbody>().isKinematic = true;
GetComponent<Collider>().isTrigger = true;
}
void Update()
{
// 获取输入的水平和垂直轴值
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
// 计算移动方向
Vector3 movement = new(moveHorizontal, 0.0f, moveVertical);
// 移动玩家
transform.Translate(movement * moveSpeed * Time.deltaTime, Space.World);
}
}
4)Tile类
虽然在Terrain类里设置了三个状态,但是我在tile里只用到了moveCost来实现一个移动速度的变化(总之能体会到享元模式的share就行啦),感兴趣的同学可以试试对其他状态的使用,比如掉进水里?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Tile : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
//Debug.Log("trigger");
if (other.CompareTag("Player")) {
other.GetComponent<Player>().moveSpeed = WorldManager.Instance.GetType(this).GetMoveCost();//实现间接访问
Debug.Log("change:" + other.GetComponent<Player>().moveSpeed);
}
}
}
4、Unity里的设置
1)添加自己想要标识的地形的tag
由于我的OnTriggerEnter用到了Player的tag,因此还需要把player物体(我为了简单用的是胶囊体)的tag设为Player
2)将Player脚本挂在Player上
3)建一个空物体把WorldManager挂上去
自己建三个材质球并赋给WorldManager
5、运行效果展示
运行后会随机生成一个地形,如下图
操控胶囊体移动可观察到在不同地形移动速度发生变化
PS.为了碰撞更容易检测建议把胶囊体设置为小于地形块的尺寸