由于最近失业在家,闲下来了想着做个小游戏。以便于以后没信号的时候可以玩玩。先上效果图并附上 源码
成品效果图思路
- 能移动的飞行物(View)
- 容纳飞行物的容器 (ViewGroup)
- 控制飞行物在容器中的位置、移动、以及碰撞检测
飞行物
首先捋一捋创建一个飞行物所需要的属性
- 类型(我的飞机、敌机、我的子弹、敌机子弹、BOSS飞机、BOSS子弹,为了方便我把爆炸效果也归类到飞行物中)
- 视图(View)
- 坐标
- 宽高
- 血量
- 威力(敌机撞到我,敌机子弹撞到我等等......)
- 飞行物的死亡标记
- 飞行物的飞行速度
- 为了方便计算,在内部算出ta的中心X和中心Y
- context
好的,屡清楚了可以开始写了
首先创建飞行物的基类
Fly
abstract class Fly {
//飞行物类型
var flyType: FlyType
//飞行物展示的view
var view: View
//飞行物的的偏移值 xy
var x = 0F
set(value) {
view.x = value
cx = (view.x + w / 2)
field = value
}
var y = 0F
set(value) {
view.y = value
cy = (view.y + h / 2)
field = value
}
//飞行物的宽高
var w = 0
var h = 0
//飞行物的血量
var HP = 100
//飞行物的碰撞威力
var power = 100
//这个飞行物是否已经死亡
var isBoom = false
//飞行物的移动速度 越小越快
var speed = 1
//上下文
var context: Context
//飞行物的中心点
var cx = 0F
var cy = 0F
}
这里飞行物基类创建好了之后开始派生子类
- 飞机(
Plane.kt
) - 子弹(
Bullet.kt
) - 爆炸(
Boom.kt
)
创建飞行物的枚举类型
FlyType
enum class FlyType {
PLANE_GCD, //我军飞机
PLANE_GMD, //敌军飞机
PLANE_BOSS, //boss飞机
BULLET_GCD, //我军子弹
BULLET_GMD, //敌军子弹
BULLET_BOSS,//boss子弹
BOOM,
OTHER //预留
}
创建飞行物的视图(View)
精神领袖
作图是不可能作图的,这辈子都不可能作图,只能靠onDraw维持生活这样子
-
飞机
PlaneView
(我机和敌机都用这个view,如果是敌机就给他旋转180°,canvas.rotate
)
竖着draw一个rect为飞机主体,横着draw两个rect为飞机的翅膀 ,再在尾部绘制火焰效果(火焰效果看这里,飞机就完成啦(点我看图
),代码我就不贴了。 -
子弹
BulletView
(我机和敌机一样都用这个View)
竖着draw一个rect为子弹主体,横着draw一个rect为子弹尾翼,再在尾部绘制火焰效果 -
爆炸效果
BoomView
实现的思路就是在view的中心绘制12个rect,之后用动画将这个12个rect沿着同一个方向移动,
在onDraw的过程中,每绘制一个之后,canvas.rotate(30)
,这样就有扩散效果了
容器
MapView
class MapView : ViewGroup {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
private var w = 0
private var h = 0
private var loadFinish = false
var onMeasureFinishListener: OnViewLoadFinishListener? = null
init {
setWillNotDraw(true)
}
/**
* 指定飞行物的位置
*/
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child != null) {
val childW = child.measuredWidth
val childH = child.measuredHeight
child.layout(0, 0, childW, childH)
}
}
}
/**
* 测量
*/
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
measureChildren(widthMeasureSpec, heightMeasureSpec)
w = measuredWidth
h = measuredHeight
if (!loadFinish) {
onMeasureFinishListener?.onFinish(w, h)
loadFinish = true
}
}
/**
* 添加飞行物
*/
fun addFly(fly: Fly) {
addView(fly.view)
}
/**
* 删除飞行物
*/
fun removeFly(fly: Fly) {
removeView(fly.view)
}
interface OnViewLoadFinishListener {
fun onFinish(w: Int, h: Int)
}
}
控制
首先提供一个简单的工厂类来获取飞机、子弹、爆炸
object FlyFactory{
/**
* 获取飞机的Fly
*/
fun getPlane(context: Context, flyType: FlyType): Plane?{......}
/**
* 获取子弹的Fly
*/
fun getBullet(context: Context, flyType: FlyType): Bullet?{......}
/**
* 获取爆炸效果的Fly
*/
fun getBoom(context: Context, flyType: FlyType): Boom? {......}
/**
* 获取爆炸View
*/
private fun getBoomView(context: Context, flyType: FlyType): View?{......}
/**
* 获取飞机或子弹的View
*/
private fun getView(context: Context, flyType: FlyType): View?{......}
}
主要的逻辑控制类FlyController
飞行物管理的集合(为了避免ConcurrentModificationException异常,这里用CopyOnWriteArrayList)
/**
* 我军飞机
*/
private var gcdPlanes: CopyOnWriteArrayList<Fly> = CopyOnWriteArrayList()
/**
* 敌军飞机集合
*/
private var gmdPlanes: CopyOnWriteArrayList<Fly> = CopyOnWriteArrayList()
/**
* 我军子弹集合
*/
private var gcdBullets: CopyOnWriteArrayList<Fly> = CopyOnWriteArrayList()
/**
* 敌军子弹集合
*/
private var gmdBullets: CopyOnWriteArrayList<Fly> = CopyOnWriteArrayList()
创建飞机
val plane = FlyFactory.getPlane(activity, flyType)!!
if (flyType == FlyType.PLANE_GCD) {
//我的飞机的位置y在屏幕的最下方,x在屏幕中心位置
plane.x = w / 2F - plane.w / 2
plane.y = h - plane.h.toFloat()
} else if (flyType == FlyType.PLANE_GMD) {
//敌机y在屏幕的最上方,x则是容器的w以内的随机位置
plane.x = (w - plane.w) * random.nextFloat()
plane.y = -plane.h * 2F
}
将飞机加入集合与容器
MapView
中
/**
* 添加Fly到集合和mapView
*/
private fun addFly(fly: Fly) {
when (fly.flyType) {
FlyType.PLANE_GCD -> {
gcdPlanes.add(fly)
}
FlyType.PLANE_GMD -> {
gmdPlanes.add(fly)
}
......省略代码
}
mapView.addFly(fly)
}
对应的有添加就有删除
/**
* 从集合和mapView中删除fly
*/
private fun removeFly(fly: Fly) {
when (fly.flyType) {
FlyType.PLANE_GMD -> {
createPlane(FlyType.PLANE_GMD)
gmdPlanes.remove(fly)
}
FlyType.PLANE_GCD -> {
gcdPlanes.remove(fly)
}
......省略代码
}
mapView.removeFly(fly)
//删除的时候标记为死亡状态
fly.boom()
}
创建子弹(子弹的初始位置为发射子弹飞机的当前位置) 所以需要传入plane
/**
* 创建子弹
*/
private fun createBullet(plane: Plane) {
val bullet = FlyFactory.getBullet(activity, plane.flyType)!!
bullet.x = (plane.cx - bullet.w / 2)
bullet.y = (plane.cy - bullet.h / 2)
}
启动线程为飞机创建子弹
/**
* 我的飞机自动发射子弹线程
*/
private fun startGcdPlaneShotThread() {
Thread {
while (true) {
activity.runOnUiThread {
if (gcdPlanes.size != 0) {
shot(gcdPlanes[0] as Plane)
}
}
SystemClock.sleep(200)
}
}.start()
}
/**
* 敌机自动发射子弹线程
*/
private fun startGmdPlaneShotThread() {
Thread {
while (true) {
//遍历敌机线程,并随机确定是否为飞机创建子弹
gmdPlanes.forEach {
activity.runOnUiThread {
if (random.nextInt(3) % 3 == 0 )
shot(it as Plane)
}
}
SystemClock.sleep(2000)
}
}.start()
}
飞机和子弹创建好之后需要立马移动它们 (飞机和子弹的飞行动画)
/**
* y轴移动Fly
*/
private fun moveFlyY(fly: Fly) {
//如果fly已经死亡
if (fly.isBoom) {
return
}
//移动的起始位置是自己的y
var start = fly.cy
//移动的目标位置,如果是敌机和敌机子弹,则是屏幕的最下方再加自身两个高度 ,我的子弹则相反
var end = when (fly.flyType) {
FlyType.BULLET_GCD -> {
-fly.h * 2
}
FlyType.PLANE_GCD -> {
return
}
FlyType.PLANE_GMD -> {
h + fly.h * 2
}
FlyType.BULLET_GMD -> {
h + fly.h * 2
}
......省略代码
}
ValueAnimator.ofFloat(start, end.toFloat())
.apply {
addUpdateListener {
//在动画过程中,如果经过碰撞检测判定飞行器已经死亡,则马上取消动画
if (fly.isBoom) {
cancel()
return@addUpdateListener
}
//更改飞行物的y值以移动飞行物
fly.y = (it.animatedValue as Float)
......省略代码
}
//动画的时间取值是起始距离到结束距离的值座位毫秒数,再乘以飞行器的速度
//speed越高则飞行越慢,再之前给敌机的speed赋值的时候可以使用随机数,让敌机飞行有块有慢
duration = (abs(start - end)).toLong() * fly.speed
interpolator = LinearInterpolator()
start()
}
}
经过上面过程之后,飞机和子弹就动起来了。接下来在动画中,需要做飞出屏幕的检测和碰撞检测
首先是飞出屏幕检测
/**
* Fly位置检测 如果已经超出屏幕则删除
*/
private fun checkFlyPosition(fly: Fly): Boolean {
//如果view已经不再屏幕内了 删除它
if (fly.x + fly.w <= -fly.w ||
fly.x >= w + fly.w ||
fly.y + fly.h <= -fly.h ||
fly.y >= h + fly.h
) {
removeFly(fly)
return false
}
return true
}
碰撞检测
/**
* 通过flyType来决定选择哪个集合中的元素与它进行碰撞检测
*/
private fun selectHitAndBeHit(fly: Fly) {
when (fly.flyType) {
FlyType.BULLET_GCD -> {
//如果是我的子弹,则与敌机集合检测
hitAndBeHit(fly, gmdPlanes)
}
FlyType.BULLET_GMD -> {
//如果是敌机的子弹则与我机检测
hitAndBeHit(fly, gcdPlanes)
}
FlyType.PLANE_GMD -> {
//...
hitAndBeHit(fly, gcdPlanes)
}
FlyType.PLANE_BOSS -> {
//...
hitAndBeHit(fly, gcdPlanes)
}
FlyType.BULLET_BOSS -> {
//...
hitAndBeHit(fly, gcdPlanes)
}
}
}
/**
* 碰撞和被碰撞的集合
*/
private fun hitAndBeHit(hitFly: Fly, beHitFlys: CopyOnWriteArrayList<Fly>) {
for (beHitFly in beHitFlys) {
//碰撞之后跳出循环
if (isCollision(
hitFly.x,
hitFly.y,
hitFly.w.toFloat(),
hitFly.h.toFloat(),
beHitFly.x,
beHitFly.y,
beHitFly.w.toFloat(),
beHitFly.h.toFloat()
)
) {
sellingHP(hitFly, beHitFly)
break
}
}
}
/**
* 碰撞检测
*/
private fun isCollision(
x1: Float,
y1: Float,
w1: Float,
h1: Float,
x2: Float,
y2: Float,
w2: Float,
h2: Float
): Boolean {
if (x1 > x2 + w2 || x1 + w1 < x2 || y1 > y2 + h2 || y1 + h1 < y2) {
return false
}
return true
}
/**
* 碰撞扣除HP
*/
private fun sellingHP(fly1: Fly, fly2: Fly) {
fly1.HP -= fly2.power
fly2.HP -= fly1.power
isDid(fly1)
isDid(fly2)
}
/**
* 飞行物是否死亡
*/
private fun isDid(fly: Fly) {
if (fly.HP <= 0) {
//创建爆炸效果
createBoom(fly)
removeFly(fly)
}
}
当碰撞发生时 如果有飞行器死亡,则会触发一个爆炸效果,位置就在飞行器死亡的位置
/**
* 创建爆炸效果
*/
private fun createBoom(fly: Fly) {
val boom = FlyFactory.getBoom(activity, fly.flyType)!!
boom.x = (fly.cx - boom.w / 2)
boom.y = (fly.cy - boom.h / 2)
mapView.addFly(boom)
val flyBoomView = boom.view as FlyBoomView
//爆炸动画结束的时候将其从mapView中删除
flyBoomView.animatorListener = object : FlyBoomView.AnimatorListener {
override fun onAnimationEnd() {
removeFly(boom)
}
}
}
完!
网友评论