Android自定义View--螺旋丸
2018-10-22 本文已影响33人
a49f87ef5d4f
0.前言
最近在做游戏,所以博客就改成双周更了。诚恳的地说,我不是一个火影迷,初中的时候看了几集,高中的时候看了几集,然后就拉倒了。我觉得嘴遁鸣人的螺旋丸挺不错的,今天准备做一个9.9元包邮版螺旋丸。看效果图
image
1.分析
1.1这个球是个立体的
立体的话这里采用opengl es进行实现
1.2球分三层
最里面是白色,碗面浅蓝,再者更蓝,最后是白色线段框架
1.3球要旋转
螺旋丸螺旋丸,自然得转圈了
2.代码
2.1绘制球体
HelicalCircle是主体,包括program和shader的创建,球坐标的创建,最后的绘制
2.1.1准备球体坐标
private fun preparePosList() {
var x0 = 0f
var y0 = 0f
var z0 = radius
var x1 = 0f
var y1 = 0f
var z1 = radius
val h = 360 / precision
val v = 360 / precision
for (i in 0 until h) {
val arc0 = Math.PI / 180 * i * precision
val arc1 = Math.PI / 180 * (i + 1) * precision
y0 = (radius * Math.cos(arc0)).toFloat()
y1 = (radius * Math.cos(arc1)).toFloat()
for (j in 0 until v) {
val arc = Math.PI / 180 * j * precision
x0 = (radius * Math.sin(arc0) * sin(arc)).toFloat()
z0 = (radius * Math.sin(arc0) * cos(arc)).toFloat()
x1 = (radius * Math.sin(arc1) * sin(arc)).toFloat()
z1 = (radius * Math.sin(arc1) * cos(arc)).toFloat()
posList.add(x0)
posList.add(y0)
posList.add(z0)
posList.add(x1)
posList.add(y1)
posList.add(z1)
}
}
pos= posList.toFloatArray()
}
这段代码生成了一个float数组,就是每个球体上点的坐标。这里采用的是球坐标,原理很简单,你可以把球体横向按照precision为梯度进行横切,然后每个切面也按照precision进行竖切,每个点的坐标计算出来后添加进一个List然后转换为float数组。
2.1.2准备program和shader
private val vertexSlgl="#version 300 es\n" +
"\n" +
"layout(location=0) in vec3 pos;\n" +
"uniform mat4 model;\n" +
"uniform mat4 view;\n" +
"uniform mat4 projection;\n" +
"void main()\n" +
"{\n" +
" gl_Position=projection*view*model*vec4(pos,1.0);\n" +
"}"
private val fragmentSlgl="#version 300 es\n" +
"precision mediump float;\n" +
"uniform vec4 aColor;\n" +
"out vec4 fragColor;\n" +
"void main()\n" +
"{\n" +
" fragColor=aColor;\n" +
"}"
private fun prepareProgram()
{
programId = createProgram(vertexSlgl, fragmentSlgl)
val vaoArray = IntArray(1)
GLES30.glGenVertexArrays(1, vaoArray, 0)
VAO = vaoArray[0]
GLES30.glBindVertexArray(VAO)
val vboArray = IntArray(1)
GLES30.glGenBuffers(1, vboArray, 0)
VBO = vboArray[0]
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, VBO)
val posBuffer = ByteBuffer.allocateDirect(4 * pos.size).order(ByteOrder.nativeOrder()).asFloatBuffer()
posBuffer.put(pos)
posBuffer.position(0)
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, 4 * pos.size, posBuffer, GLES30.GL_STATIC_DRAW)
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 3 * 4, 0)
GLES30.glEnableVertexAttribArray(0)
}
private fun createProgram(vertexSource: String, fragmentSource: String): Int {
val vertexShader = createShader(GLES30.GL_VERTEX_SHADER, vertexSource)
val fragmentShader = createShader(GLES30.GL_FRAGMENT_SHADER, fragmentSource)
return createProgram(vertexShader, fragmentShader)
}
private fun createShader(type: Int, source: String): Int {
val shaderId = GLES30.glCreateShader(type)
if (shaderId <= 0) {
Log.d(TAG, "create shader failed")
}
GLES30.glShaderSource(shaderId, source)
GLES30.glCompileShader(shaderId)
val status = IntArray(1)
GLES30.glGetShaderiv(shaderId, GLES30.GL_COMPILE_STATUS, status, 0)
if (status[0] <= 0) {
Log.d(TAG, "compile shader failed")
val infoLog = GLES30.glGetShaderInfoLog(shaderId)
Log.d(TAG, infoLog)
GLES30.glDeleteShader(shaderId)
}
return shaderId
}
private fun createProgram(vertexShader: Int, fragmentShader: Int): Int {
val programId = GLES30.glCreateProgram()
if (programId <= 0) {
Log.d(TAG, "create program failed")
}
GLES30.glAttachShader(programId, vertexShader)
GLES30.glAttachShader(programId, fragmentShader)
GLES30.glLinkProgram(programId)
val status = IntArray(1)
GLES30.glGetProgramiv(programId, GLES30.GL_LINK_STATUS, status, 0)
if (status[0] <= 0) {
Log.d(TAG, "link program failed")
val infoLog = GLES30.glGetProgramInfoLog(programId)
Log.d(TAG, infoLog)
}
return programId
}
这段代码就是创建shader和program,很简单,没什么可说的。
2.1.2绘制
fun draw(aspect:Float,r:Float=0.0f,g:Float=0.0f,b:Float=1.0f,a:Float=0.5f,scale:Float=1.0f)
{
degree+=10
GLES30.glUseProgram(programId)
val colorLocation=GLES30.glGetUniformLocation(programId,"aColor")
GLES30.glUniform4f(colorLocation,r,g,b,a)
val modelMatrixLocation=GLES30.glGetUniformLocation(programId,"model")
Matrix.setIdentityM(modelMatrix,0)
Matrix.scaleM(modelMatrix,0,scale,scale,scale)
Matrix.rotateM(modelMatrix,0, Math.toRadians(degree).toFloat(),0f,1f,0f)
GLES30.glUniformMatrix4fv(modelMatrixLocation,1,false,modelMatrix,0)
val viewMatrixLocation=GLES30.glGetUniformLocation(programId,"view")
Matrix.setLookAtM(viewMatrix,0,0f,0f,10f,0f,0f,0f,0f,1f,0f)
GLES30.glUniformMatrix4fv(viewMatrixLocation,1,false,viewMatrix,0)
val projectionMatrixLocation=GLES30.glGetUniformLocation(programId,"projection")
Matrix.perspectiveM(projectionMatrix,0,45.0f, aspect,0.1f,100f)
GLES30.glUniformMatrix4fv(projectionMatrixLocation,1,false,projectionMatrix,0)
GLES30.glDrawArrays(model,0,pos.size / 3)
}
}
绘制球体,设置球体颜色和缩放程度
2.HelicalCircleRender
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
helicalCircle.prepare()
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
GLES30.glViewport(0, 0, width, height)
screenWidth = width.toFloat()
screenHeight = height.toFloat()
}
override fun onDrawFrame(gl: GL10?) {
GLES30.glEnable(GLES30.GL_BLEND)
GLES30.glClearColor(0f,0f,0f,1.0f)
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT or GLES30.GL_DEPTH_BUFFER_BIT)
val aspect=screenWidth / screenHeight
GLES30.glDisable(GLES30.GL_DEPTH_TEST)
helicalCircle.model=GLES30.GL_TRIANGLE_STRIP
helicalCircle.draw(aspect,r=0.67f,g=0.85f,b=0.98f,a=0.5f,scale = 0.99f*scale)
helicalCircle.draw(aspect,r=0.85f,g=0.96f,b=0.99f,a=0.5f,scale= 0.5f*scale)
helicalCircle.draw(aspect,r=0.97f,g=1.0f,b=0.99f,a=0.5f,scale= 0.3f*scale)
GLES30.glEnable(GLES30.GL_DEPTH_TEST)
helicalCircle.model=GLES30.GL_LINE_STRIP
helicalCircle.draw(aspect,r=1.0f,g=1.0f,b=1.0f,a=0.2f,scale = 0.998f*scale)
}
onSurfaceCreated里进行球坐标的计算和program以及shader的准备,在onDrawFrame里绘制三个球
2.3旋转
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action)
{
MotionEvent.ACTION_DOWN->{
touchX=event.x
}
MotionEvent.ACTION_MOVE->{
val scaleOffset=(event.x-touchX)/width
render.scale+=scaleOffset
touchX=event.x
}
}
return true
}
复写HelicalCircleView的onTouchEvent方法,通过修改HelicalCircleRender的scale来对球体进行缩放。
3源码
image关注我的公众号