从0开始的Oasis Engine学习笔记(六) —— 2D场景布置

这是我参与更文挑战的第6天,活动详情查看: 更文挑战

上一章介绍了Oasis里的坐标系,本篇文章就介绍如何将一个2D游戏的场景布置出来

精灵资源加载

Sprite 是 2D 图形对象,用于角色、道具、子弹以及一些其他 2D 游戏要素。这些图形是从 Texture2D 获得的。Sprite 类主要标识应用于特定Sprite的图像部分。然后 Entity 上的 SpriteRenderer 组件可以使用此信息来实际显示图形。

我们先加载一个素材看看效果

   // 加载纹理资源
  const groundTexture: Texture2D = await engine.resourceManager.load(textureList[0])
  
  // 创建精灵实体
  const groundEntity = rootEntity.createChild('groundSprite')
  const groundRenderer = groundEntity.addComponent(SpriteRenderer)
  const groundsprite = new Sprite(engine, groundTexture)
  groundRenderer.sprite = groundsprite
  
  // 设置实体坐标
  const groundScreenpoint = new Vector3(0, 0)
  const groundWorldpoint = new Vector3()
  cameramash.screenToWorldPoint(groundScreenpoint, groundWorldpoint)
  groundEntity.transform.position = groundWorldpoint
复制代码

image.png

设置模型坐标原点

如果仔细观察的话可以发现在屏幕的左上角有1/4个方块,为方便观察我们使用
groundEntity.transform.scale = new Vector3(5, 5)将素材放大5倍。

image.png
我们将实体的位置设置在屏幕(0, 0)上,而Oasis里默认的模型坐标原点在中心点上,所以此时只能看到1/4个方块。如果我们想要看到整个素材就需要设置坐标为
(groundEntity.width / 2, groundEntity.height / 2)。而我们可以使用 sprite.pivot 来重新设置模型坐标的原点。

详情介绍可以查看官网的精灵资源(注:rect方法在V0.4版本中为region

我们这里将左上角设置为原点,便可以看到一个完整的方块

groundsprite.pivot = new Vector2(0, 1)

image.png

素材分辨率设置

我这里的引用素材大小为48*48
image.png
然而,渲染后的内容明显小于素材实际大小

image.png
这是因为在Sprite下面有一个pixelsPerUnit参数,我们先来看一下官网对这个参数的介绍

The number of pixels in the sprite that correspond to one unit in world space.(对应世界空间中一个单位的精灵中的像素数。)

我对这个介绍的理解是:

在世界坐标下,一个单位的长度对应多少屏幕像素点。

使用以下代码可以求出一个单位的长度对应多少屏幕像素点

  const worldPoint = new Vector3(0, 0, 0)
  const screenOut = new Vector4()
  cameramash.worldToScreenPoint(worldPoint, screenOut)
  const worldPoint1 = new Vector3(1, 0, 0)
  const screenOut1 = new Vector4()
  cameramash.worldToScreenPoint(worldPoint1, screenOut1)
  console.log(`pixelsPerUnit:${screenOut1.x - screenOut.x}`) // 128
复制代码

我们可以使用console.log(Sprite.pixelsPerUnit)来查看是否也为128

console.log(groundsprite.pixelsPerUnit)   // 128
复制代码

我们将pixelsPerUnit设置为10 可以看到素材相较与pixelsPerUnit为128时大了很多

    groundsprite.pixelsPerUnit = 10
复制代码

image.png

所以,可以得出一个简单的结论:在世界坐标下,如果一个单位的长度对应更多的屏幕像素则图片会变得更小;反之更大。即,

pixelsPerUnit 数字越大,图片越小。

所以就可以将素材通过pixelsPerUnit调整到合适的大小,这里将pixelsPerUnit调整为50

groundsprite.pixelsPerUnit = 50

自适应缩放

CPT2106242355-982x633.gif
在游戏场景中,素材的大小应该可以随着用户屏幕的宽高自适应调整,所以精灵的宽高需要通过屏幕计算而来。

这里渲染了两排,每排25个精灵

  // 获取canvas的宽
  const canvasX = engine.canvas.width
  
  // 确定需要25个精灵后,用Canvas的宽除以每个精灵的宽再除以25得到每个精灵需要缩放的倍数
  const scaleTimes = canvasX / 25 / groundTexture.width
  
  // 最后通过transform.scale设置实体的变换
  groundEntity.transform.scale = new Vector3(scaleTimes, scaleTimes)

复制代码

这样无论用户显示器的宽度为多少,都能够按比例显示大小,再通过两个while循环渲染出来

两排地面完整代码

// 获取精灵材质
  const groundTexture: Texture2D = await engine.resourceManager.load(textureList[0])
  // 设置初始行列变量
  let i = 0
  let j = 0
  // 计算缩放比例
  const scaleTimes = canvasX / 25 / groundTexture.width
  
  // 双循环渲染
  while (j < 2) {
    while (i < 25) {
    
       // 创建精灵实体
      const groundEntity = rootEntity.createChild(`groundSprite_${i}`)
      const groundRenderer = groundEntity.addComponent(SpriteRenderer)
      const groundsprite = new Sprite(engine, groundTexture)
      
      // 设置模型中心为左上角
      groundsprite.pivot = new Vector2(0, 1)
      
      // 设置pixelsPerUnit值为50
      groundsprite.pixelsPerUnit = 50
      groundRenderer.sprite = groundsprite
      
      // 计算实体屏幕坐标系X,Y轴位置
      const pointX = groundTexture.width * i * scaleTimes
      const pointY = canvasY - groundTexture.height * scaleTimes * (j + 1)
      
      // 转换为世界坐标
      const screenpoint = new Vector3(pointX, pointY)
      const out = new Vector3()
      cameramash.screenToWorldPoint(screenpoint, out)
      
      // 设置位置和缩放
      groundEntity.transform.position = out
      groundEntity.transform.scale = new Vector3(scaleTimes, scaleTimes)
      
      i++
    }
    i = 0
    j++
  }
复制代码

再通过同样的方法添加3朵云和一个角色,设置一下背景颜色

image.png
一个基本的场景布置就完成了

完整代码

import {
  Camera,
  WebGLEngine,
  Vector2,
  Vector3,
  Texture2D,
  SpriteRenderer,
  Sprite,
  AssetType,
  BackgroundMode
} from 'oasis-engine'

export async function createOasis (): Promise<void> {
  // 初始化Engine
  const engine = new WebGLEngine('canvas')
  engine.canvas.resizeByClientSize()

  // 获取场景根实体
  const scene = engine.sceneManager.activeScene
  const rootEntity = scene.createRootEntity('root')
  scene.background.mode = BackgroundMode.SolidColor
  scene.background.solidColor.setValue(0.76, 0.76, 0.74, 1)

  // 获取Canvas宽高
  const canvasX = engine.canvas.width
  const canvasY = engine.canvas.height

  // 创建一个相机实体
  const cameraEntity = rootEntity.createChild('camera_entity')
  const cameramash = cameraEntity.addComponent(Camera)
  cameraEntity.transform.position = new Vector3(0, 0, 20)
  cameramash.isOrthographic = true

  // 创建资源
  const textureList = [
    {
      url: require('../assets/ground.png'),
      type: AssetType.Texture2D
    }, {
      url: require('../assets/cloud.png'),
      type: AssetType.Texture2D
    }, {
      url: require('../assets/i1.png'),
      type: AssetType.Texture2D
    }
  ]

  // 创建地面实体
  const groundTexture: Texture2D = await engine.resourceManager.load(textureList[0])
  let i = 0
  let j = 0
  const scaleTimes = canvasX / 25 / groundTexture.width
  while (j < 2) {
    while (i < 25) {
      const groundEntity = rootEntity.createChild(`groundSprite_${i}`)
      const groundRenderer = groundEntity.addComponent(SpriteRenderer)
      const groundsprite = new Sprite(engine, groundTexture)
      groundsprite.pivot = new Vector2(0, 1)
      groundsprite.pixelsPerUnit = 50
      groundRenderer.sprite = groundsprite
      const pointX = groundTexture.width * i * scaleTimes
      const pointY = canvasY - groundTexture.height * scaleTimes * (j + 1)
      const screenpoint = new Vector3(pointX, pointY)
      const out = new Vector3()
      cameramash.screenToWorldPoint(screenpoint, out)
      groundEntity.transform.position = out
      groundEntity.transform.scale = new Vector3(scaleTimes, scaleTimes)
      i++
    }
    i = 0
    j++
  }

  // 创建角色实体
  const roleTexture: Texture2D = await engine.resourceManager.load(textureList[2])
  const roleEntity = rootEntity.createChild('roleSprite')
  const roleRenderer = roleEntity.addComponent(SpriteRenderer)
  const rolesprite = new Sprite(engine, roleTexture)
  rolesprite.pivot = new Vector2(0, 1)
  rolesprite.pixelsPerUnit = 50
  roleRenderer.sprite = rolesprite
  const roleX = groundTexture.height * scaleTimes * 5
  const roleY = canvasY - groundTexture.height * scaleTimes * 2 - roleTexture.height * scaleTimes
  const rolepoint = new Vector3(roleX, roleY)
  const roleout = new Vector3()
  cameramash.screenToWorldPoint(rolepoint, roleout)
  roleEntity.transform.position = roleout
  roleEntity.transform.scale = new Vector3(scaleTimes, scaleTimes)

  // 创建云实体
  const cloud1Texture: Texture2D = await engine.resourceManager.load(textureList[1])
  let cloudPlace = 0
  while (cloudPlace < 3) {
    const cloud1Entity = rootEntity.createChild(`cloud1Sprite_${cloudPlace}`)
    const cloud1Renderer = cloud1Entity.addComponent(SpriteRenderer)
    const cloud1sprite = new Sprite(engine, cloud1Texture)
    cloud1sprite.pivot = new Vector2(0, 1)
    cloud1sprite.pixelsPerUnit = 50
    cloud1Renderer.sprite = cloud1sprite
    const cloud1ponintX = canvasX * cloudPlace / 3 + cloud1Texture.width
    const cloud1screenpoint = new Vector3(cloud1ponintX, 0)
    const cloud1out = new Vector3()
    cameramash.screenToWorldPoint(cloud1screenpoint, cloud1out)
    cloud1Entity.transform.position = cloud1out
    cloudPlace++
  }

  // 启动引擎
  engine.run()
}

复制代码

end

如果可以的话,可以点个小小的赞
image.png

respect by myself

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享