当前位置 主页 > 网站技术 > 代码类 >

    基于SceneForm实现子弹射击(绘制子弹运行轨迹)

    栏目:代码类 时间:2019-11-25 09:06

    基于 SceneForm 实现的子弹射击(绘制子弹运行轨迹)

    Sceneform 框架很强大,不了解 Sceneform 的时候,觉得要想做 3D 场景需要会 OpenGL,而 OpenGL 的学习曲线很陡;接触到这个框架之后觉得小白也可以很快上手,甚至可以实现第一人称射击的效果

    注:自己学习 SceneForm 有一段时间了,不过没有发现模拟重力场的接口,不知道是不是自己漏掉了

    模拟射击效果的思路其实很简单

    1、加载一个子弹模型
    2、规划子弹由近及远的轨迹
    3、绘制子弹的运行轨迹

    子弹运行轨迹的逻辑代码;代码中涉及的 CleanArFragment 在之前的《ARCore 的 SceneForm 框架在没有 Plane 情况下的绘制 3D 模型》已经给出;另外需要自行提供一个纹理图片,即代码中的 R.drawable.texture。

    class MainActivity : AppCompatActivity() {
    
     var arFragment : CleanArFragment? = null
     var camera : Camera? = null
     var size = Point(); //屏幕尺寸,控制子弹发射的初始位置
     var bullet : ModelRenderable? = null
     var scene : Scene? = null
     val SHOT = 0x1101  //绘制过程轨迹信号
     val SHOT_OVER = 0x1102 //清除子弹模型信号
    
     var handler = object : Handler() {
      override fun handleMessage(msg : Message)
      {
       if (msg.what == SHOT) { //绘制移动过程中的轨迹
        var currentStatus = msg.obj as CurrentStatus
        currentStatus.node.worldPosition = currentStatus.status
       } else if (msg.what == SHOT_OVER) { //一次射击完成,清除屏幕的子弹
        var node = msg.obj as Node
        scene!!.removeChild(node)
       }
      }
     }
    
     override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
    
      // 获取屏幕尺寸
      val display = windowManager.defaultDisplay
      display.getRealSize(size)
      arFragment = this.supportFragmentManager.findFragmentById(R.id.arFragment) as CleanArFragment
      arFragment!!.arSceneView.planeRenderer.isEnabled = false  //禁止 sceneform 框架的平面绘制
      scene = arFragment!!.arSceneView.scene
      camera = scene!!.camera
    
      initbullet()
      shootButton.setOnClickListener(listener)
     }
    
     var listener : View.OnClickListener = object : View.OnClickListener{
      override fun onClick(v: View?) {
       shoot()
      }
     }
    
     @TargetApi(Build.VERSION_CODES.N)
     //初始化子弹模型
     private fun initbullet() {
      Texture.builder().setSource(this@MainActivity, R.drawable.texture).build()
       .thenAccept(
        { texture ->
        MaterialFactory.makeOpaqueWithTexture(this@MainActivity, texture)
         .thenAccept { material ->
          // 设置子弹模型为球体
          bullet = ShapeFactory.makeSphere(0.1f, Vector3(0f, 0f, 0f), material) }
        }
       )
     }
    
     private fun shoot() {
      //从屏幕发出的射线,对应子弹的运行轨迹
      var ray = camera!!.screenPointToRay(size.x / 2f, size.y / 2f);
      var node = Node() //子弹节点
      node.renderable = bullet //子弹节点加载子弹模型
      scene!!.addChild(node)
    
      Thread(object : Runnable{
       override fun run() {
    
        //子弹射击过程中的轨迹,子线程处理轨迹事件,主线程改变轨迹位置
        for (i in 1 .. 200 ) { //子弹射程 20 m
         var stepLen = i;
         var currentPoint = ray.getPoint(stepLen * 0.1f)
         var msg = handler.obtainMessage()
         msg.what = SHOT
         msg.obj = CurrentStatus(node, currentPoint)
         handler.sendMessage(msg)
        }
    
        //子弹超出距离后,从屏幕清除掉
        var msg = handler.obtainMessage()
        msg.what = SHOT_OVER
        msg.obj = node
        handler.sendMessage(msg)
       }
      }).start()
     }
    
     // 子线程和主线程穿点的数据类
     data class CurrentStatus(var node : Node, var status : Vector3)
    }