Game Programming Patterns 阅读笔记 3

回顾 Flyweight 享元模式 (代码以Gdscript呈现)

Flyweight的两种翻译

  • 两种翻译的联系可以解释这个设计模式的思路:用”享元“实现了”蝇量“

森林的例子

  • 森林由树组成

    • 树由Mesh、Texture、Position、Height、Color等资源和参数组成

    • # Tree.gd (单个树的实例数据)
      class_name Tree
      
      var model: TreeModel
      var position: Vector3
      var height: float
      var thickness: float
      var bark_tint: Color
      var leaf_tint: Color
  • 有多少树,就要消耗多少倍的资源,在同1帧内给GPU这么多资源实在是太多了

    • 好在树长得都差不多,他们共享很多资源和属性

    • 比如可以让Mesh、Texture共享

    • # TreeModel.gd (共享资源)
      extends Resource
      class_name TreeModel
      
      @export var mesh: Mesh
      @export var bark_texture: Texture2D
      @export var leaves_texture: Texture2D
  • 这就是现代图形API所支持的”实例渲染“,享元模式是硬件级支持的设计模式

  • 其实只是把多个实例中不变的东西打包共享给所有该对象的实例

瓦片地面的例子

  • 不要每个网格都分配一个瓦片实例

  • 网格之间的唯一区别是位置不同,所以,所有草地瓦片共用一个实例,所有河流瓦片共用一个实例,所有丘陵瓦片共用一个实例

  • 实现如下:

    • # Terrain.gd (享元对象)
      class_name Terrain
      
      var movement_cost: int
      var is_water: bool
      var texture: Texture2D
      
      func _init(p_movement_cost: int, p_is_water: bool, p_texture: Texture2D):
      	movement_cost = p_movement_cost
      	is_water = p_is_water
      	texture = p_texture
      
      func get_movement_cost() -> int:
      	return movement_cost
      
      func is_water() -> bool:
      	return is_water
      
      func get_texture() -> Texture2D:
      	return texture
    • # World.gd (地形网格)
      extends Node
      
      const WIDTH := 100
      const HEIGHT := 100
      
      var tiles: Array[Array[Vector2i]]  # 存储 Terrain 引用
      
      @onready var grass_terrain: Terrain = Terrain.new(1, false, preload("res://grass.png"))
      @onready var hill_terrain: Terrain = Terrain.new(3, false, preload("res://hill.png"))
      @onready var river_terrain: Terrain = Terrain.new(2, true, preload("res://water.png"))
      
      func _ready():
      	_generate_terrain()
      
      func _generate_terrain():
      	tiles.resize(WIDTH)
      	for x in WIDTH:
      		tiles[x] = []
      		tiles[x].resize(HEIGHT)
      		for y in HEIGHT:
      			if randi() % 10 == 0:
      				tiles[x][y] = hill_terrain
      			else:
      				tiles[x][y] = grass_terrain
      	
      	# 添加一条河
      	var river_x := randi() % WIDTH
      	for y in HEIGHT:
      		tiles[river_x][y] = river_terrain
      
      func get_tile(x: int, y: int) -> Terrain:
      	return tiles[x][y]
  • 用例如下:

    • var tile: Terrain = world.get_tile(2, 3)
      print(tile.get_movement_cost())
      print(tile.is_water())

另见

  • 和 ”工厂方法(Factory Method)“、懒加载、对象池结合享元的创建和存储会更好

引用