├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── planewars ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jmj │ │ └── planewars │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jmj │ │ │ └── planewars │ │ │ ├── App.kt │ │ │ ├── activity │ │ │ └── MainActivity.kt │ │ │ ├── fly │ │ │ ├── FlyController.kt │ │ │ ├── cons │ │ │ │ └── FlyType.kt │ │ │ ├── flyfactory │ │ │ │ └── FlyFactory.kt │ │ │ ├── flyobject │ │ │ │ ├── Boom.kt │ │ │ │ ├── Bullet.kt │ │ │ │ ├── Fly.kt │ │ │ │ └── Plane.kt │ │ │ ├── view │ │ │ │ ├── BaseView.kt │ │ │ │ ├── BulletView.kt │ │ │ │ ├── FireView.kt │ │ │ │ ├── FlyBoomView.kt │ │ │ │ ├── MapView.kt │ │ │ │ ├── PlaneView.kt │ │ │ │ └── ScrollStarsView.kt │ │ │ └── viewtools │ │ │ │ └── FlyFire.kt │ │ │ └── tools │ │ │ ├── ExpansionAny.kt │ │ │ └── ExpansionContext.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── plane.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── jmj │ └── planewars │ └── ExampleUnitTest.kt ├── build.gradle ├── gif └── 3A0D5009E4438B045A19CA0ACC6754BF.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://github.com/JiangMingJieJie/PlaneWars/blob/master/gif/3A0D5009E4438B045A19CA0ACC6754BF.gif) 2 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | buildToolsVersion "29.0.2" 10 | defaultConfig { 11 | applicationId "com.jmj.planewars" 12 | minSdkVersion 21 13 | targetSdkVersion 28 14 | versionCode 1 15 | versionName "1.0" 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 29 | implementation 'androidx.appcompat:appcompat:1.1.0' 30 | implementation 'androidx.core:core-ktx:1.0.2' 31 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 32 | testImplementation 'junit:junit:4.12' 33 | androidTestImplementation 'androidx.test:runner:1.2.0' 34 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 35 | } 36 | -------------------------------------------------------------------------------- /app/planewars: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/planewars -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/jmj/planewars/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.jmj.planewars", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/App.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars 2 | 3 | import android.app.Application 4 | 5 | class App : Application() { 6 | companion object { 7 | private lateinit var instance: App 8 | fun getInstance()= instance 9 | } 10 | 11 | override fun onCreate() { 12 | super.onCreate() 13 | instance = this 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.activity 2 | 3 | import android.animation.ValueAnimator 4 | import android.annotation.SuppressLint 5 | import android.content.DialogInterface 6 | import android.os.Bundle 7 | import android.view.View 8 | import androidx.appcompat.app.AlertDialog 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.core.animation.doOnEnd 11 | import com.jmj.planewars.R 12 | import com.jmj.planewars.fly.FlyController 13 | import com.jmj.planewars.fly.cons.FlyType 14 | import kotlinx.android.synthetic.main.activity_main.* 15 | 16 | 17 | class MainActivity : AppCompatActivity() { 18 | 19 | private lateinit var flyController: FlyController 20 | private var killAnim2One: ValueAnimator? = null 21 | private var killAnim2Zero: ValueAnimator? = null 22 | private var lastAnimViewId = 0 23 | private var killCount = 0 24 | private var killBossCount = 0 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | setContentView(R.layout.activity_main) 28 | 29 | flyController = FlyController(this, mapView) 30 | 31 | flyController.onGameOverListener = object : FlyController.OnGameProgressListener { 32 | override fun onGameOver() { 33 | AlertDialog.Builder(this@MainActivity) 34 | .setMessage("游戏结束! 点击确定继续游戏,点击取消退出游戏") 35 | .setNegativeButton("取消") { _: DialogInterface, _: Int -> 36 | finish() 37 | }.setCancelable(false) 38 | .setPositiveButton("确定") { _: DialogInterface, _: Int -> 39 | flyController.reStart() 40 | }.show() 41 | } 42 | 43 | @SuppressLint("SetTextI18n") 44 | override fun onKill(flyType: FlyType) { 45 | super.onKill(flyType) 46 | if (flyType == FlyType.PLANE_GMD) { 47 | killCount++ 48 | tv_kill_count.text = "击败 $killCount" 49 | killAnim(tv_kill_count) 50 | } else if (flyType == FlyType.PLANE_BOSS) { 51 | killBossCount++ 52 | tv_kill_boss_count.text = "击败 $killBossCount" 53 | killAnim(tv_kill_boss_count) 54 | } 55 | } 56 | } 57 | } 58 | 59 | 60 | private fun killAnim(view: View) { 61 | if (view.id == lastAnimViewId) { 62 | killAnim2One?.cancel() 63 | killAnim2Zero?.cancel() 64 | } else { 65 | lastAnimViewId = view.id 66 | } 67 | killAnim2One = ValueAnimator.ofFloat(view.alpha, 1F).apply { 68 | addUpdateListener { 69 | view.alpha = it.animatedValue as Float 70 | } 71 | duration = 500 72 | doOnEnd { 73 | 74 | killAnim2Zero = ValueAnimator.ofFloat(view.alpha, 0F).apply { 75 | addUpdateListener { 76 | view.alpha = it.animatedValue as Float 77 | } 78 | duration = 1500 79 | start() 80 | } 81 | } 82 | start() 83 | } 84 | } 85 | 86 | 87 | override fun onDestroy() { 88 | super.onDestroy() 89 | flyController.quitGame() 90 | } 91 | 92 | override fun onBackPressed() { 93 | 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/FlyController.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly 2 | 3 | import android.animation.ValueAnimator 4 | import android.annotation.SuppressLint 5 | import android.app.Activity 6 | import android.os.SystemClock 7 | import android.view.MotionEvent 8 | import android.view.View 9 | import android.view.animation.LinearInterpolator 10 | import androidx.core.animation.doOnEnd 11 | import com.jmj.planewars.fly.cons.FlyType 12 | import com.jmj.planewars.fly.flyfactory.FlyFactory 13 | import com.jmj.planewars.fly.flyobject.Fly 14 | import com.jmj.planewars.fly.flyobject.Plane 15 | import com.jmj.planewars.fly.view.FlyBoomView 16 | import com.jmj.planewars.fly.view.MapView 17 | import com.jmj.planewars.fly.view.PlaneView 18 | import com.jmj.planewars.tools.myLog 19 | import java.lang.Math.abs 20 | import java.util.* 21 | import java.util.concurrent.CopyOnWriteArrayList 22 | 23 | 24 | class FlyController(private var activity: Activity, private var mapView: MapView) { 25 | /** 26 | * 我军飞机 27 | */ 28 | private var gcdPlanes: CopyOnWriteArrayList = CopyOnWriteArrayList() 29 | /** 30 | * 敌军飞机集合 31 | */ 32 | private var gmdPlanes: CopyOnWriteArrayList = CopyOnWriteArrayList() 33 | /** 34 | * 我军子弹集合 35 | */ 36 | private var gcdBullets: CopyOnWriteArrayList = CopyOnWriteArrayList() 37 | /** 38 | * 敌军子弹集合 39 | */ 40 | private var gmdBullets: CopyOnWriteArrayList = CopyOnWriteArrayList() 41 | /** 42 | * 屏幕内的敌军飞机数量上限 43 | */ 44 | private var gmdPlaneCount = 10 45 | /** 46 | * 地图的宽高 47 | */ 48 | private var w = 0 49 | private var h = 0 50 | 51 | private var isGameOver = false 52 | private var isQuitGame = false 53 | 54 | private var random = Random() 55 | 56 | var onGameOverListener: OnGameProgressListener? = null 57 | 58 | 59 | init { 60 | /** 61 | * mapView测量完成 62 | */ 63 | mapView.onMeasureFinishListener = object : MapView.OnViewLoadFinishListener { 64 | override fun onFinish(w: Int, h: Int) { 65 | this@FlyController.w = w 66 | this@FlyController.h = h 67 | addGcdPlane() 68 | addGmdPlane() 69 | startBossTimer() 70 | startGmdPlaneShotThread() 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * 重新开始游戏 77 | */ 78 | fun reStart() { 79 | isGameOver = false 80 | addGcdPlane() 81 | } 82 | 83 | 84 | /** 85 | * 我的飞机自动发射子弹线程 86 | */ 87 | private fun startGcdPlaneShotThread() { 88 | Thread { 89 | while (!isGameOver && !isQuitGame) { 90 | activity.runOnUiThread { 91 | if (gcdPlanes.size != 0) { 92 | shot(gcdPlanes[0] as Plane) 93 | } 94 | } 95 | 96 | SystemClock.sleep(200) 97 | } 98 | }.start() 99 | } 100 | 101 | /** 102 | * 敌机自动发射子弹线程 103 | */ 104 | private fun startGmdPlaneShotThread() { 105 | Thread { 106 | while (!isQuitGame) { 107 | gmdPlanes.forEach { 108 | activity.runOnUiThread { 109 | if (random.nextInt(3) % 3 == 0 || it.flyType == FlyType.PLANE_BOSS) 110 | shot(it as Plane) 111 | } 112 | } 113 | SystemClock.sleep(2000) 114 | 115 | } 116 | }.start() 117 | } 118 | 119 | /** 120 | * 定时刷boss 121 | */ 122 | private fun startBossTimer() { 123 | Timer().schedule(object : TimerTask() { 124 | override fun run() { 125 | activity.runOnUiThread { 126 | createPlane(FlyType.PLANE_BOSS) 127 | } 128 | } 129 | }, (random.nextInt(3) + 1) * 2000.toLong()) 130 | } 131 | 132 | 133 | /** 134 | * 添加我的飞机 135 | */ 136 | fun addGcdPlane() { 137 | createPlane(FlyType.PLANE_GCD) 138 | startGcdPlaneShotThread() 139 | } 140 | 141 | /** 142 | * 添加敌机 143 | */ 144 | private fun addGmdPlane() { 145 | for (i in 0 until gmdPlaneCount) { 146 | createPlane(FlyType.PLANE_GMD) 147 | } 148 | } 149 | 150 | 151 | /** 152 | * 创建飞机并添加到集合 153 | */ 154 | private fun createPlane(flyType: FlyType) { 155 | val plane = FlyFactory.getPlane(activity, flyType)!! 156 | 157 | if (flyType == FlyType.PLANE_GCD) { 158 | //我的飞机的位置 159 | plane.x = w / 2F - plane.w / 2 160 | plane.y = h - plane.h.toFloat() 161 | (plane.view as PlaneView).openMask() 162 | openFlyDrag(plane) 163 | } else if (flyType == FlyType.PLANE_GMD) { 164 | //敌机的位置 165 | plane.x = (w - plane.w) * random.nextFloat() 166 | plane.y = -plane.h * 2F 167 | } else { 168 | //boss机的位置 169 | plane.x = (w - plane.w) * random.nextFloat() 170 | plane.y = -plane.h * 2F 171 | moveFlyX(plane) 172 | } 173 | 174 | addFly(plane) 175 | 176 | moveFlyY(plane) 177 | } 178 | 179 | 180 | /** 181 | * 创建爆炸效果 182 | */ 183 | private fun createBoom(fly: Fly) { 184 | val boom = FlyFactory.getBoom(activity, fly.flyType)!! 185 | boom.x = (fly.cx - boom.w / 2) 186 | boom.y = (fly.cy - boom.h / 2) 187 | mapView.addFly(boom) 188 | 189 | val flyBoomView = boom.view as FlyBoomView 190 | //爆炸动画结束的时候将其从mapView中删除 191 | flyBoomView.animatorListener = object : FlyBoomView.AnimatorListener { 192 | override fun onAnimationEnd() { 193 | removeFly(boom) 194 | } 195 | } 196 | } 197 | 198 | /** 199 | * 创建子弹 200 | */ 201 | private fun createBullet(plane: Plane) { 202 | val bullet = FlyFactory.getBullet(activity, plane.flyType)!! 203 | 204 | bullet.x = (plane.cx - bullet.w / 2) 205 | 206 | bullet.y = (plane.cy - bullet.h / 2) 207 | 208 | addFly(bullet) 209 | 210 | moveFlyY(bullet) 211 | } 212 | 213 | /** 214 | * 发射子弹 215 | */ 216 | private fun shot(plane: Plane) { 217 | createBullet(plane) 218 | } 219 | 220 | 221 | /** 222 | * y轴移动Fly 223 | */ 224 | private fun moveFlyY(fly: Fly) { 225 | //如果fly已经死亡 226 | if (fly.isBoom) { 227 | return 228 | } 229 | 230 | var start = fly.cy 231 | 232 | var end = when (fly.flyType) { 233 | FlyType.BULLET_GCD -> { 234 | -fly.h * 2 235 | } 236 | FlyType.PLANE_GCD -> { 237 | return 238 | } 239 | FlyType.PLANE_GMD -> { 240 | h + fly.h * 2 241 | } 242 | FlyType.BULLET_GMD -> { 243 | h + fly.h * 2 244 | } 245 | FlyType.PLANE_BOSS -> { 246 | 0 247 | } 248 | else -> { 249 | h + fly.h * 2 250 | } 251 | } 252 | 253 | 254 | ValueAnimator.ofFloat(start, end.toFloat()) 255 | .apply { 256 | addUpdateListener { 257 | //如果fly已经死亡 258 | if (fly.isBoom) { 259 | cancel() 260 | return@addUpdateListener 261 | } 262 | 263 | //移动Fly 264 | fly.y = (it.animatedValue as Float) 265 | 266 | //超出屏幕检测 267 | if (checkFlyPosition(fly)) { 268 | selectHitAndBeHit(fly) 269 | } 270 | } 271 | duration = (abs(start - end)).toLong() * fly.speed 272 | interpolator = LinearInterpolator() 273 | start() 274 | } 275 | 276 | } 277 | 278 | /** 279 | * X轴移动Fly 280 | */ 281 | private fun moveFlyX(fly: Fly) { 282 | //如果fly已经死亡 283 | if (fly.isBoom) { 284 | return 285 | } 286 | 287 | var start = fly.x 288 | //左移动或者右移动 289 | var end = if (fly.x < w - fly.w) { 290 | w.toFloat() - fly.w 291 | } else { 292 | 0F 293 | } 294 | ValueAnimator.ofFloat(start, end).apply { 295 | addUpdateListener { 296 | //如果fly已经死亡 297 | if (fly.isBoom) { 298 | cancel() 299 | return@addUpdateListener 300 | } 301 | fly.x = it.animatedValue as Float 302 | //超出屏幕检测 303 | if (checkFlyPosition(fly)) { 304 | selectHitAndBeHit(fly) 305 | } 306 | } 307 | duration = (abs(start - end)).toLong() * fly.speed 308 | interpolator = LinearInterpolator() 309 | 310 | doOnEnd { 311 | //递归调用,实现左右不停移动 312 | moveFlyX(fly) 313 | } 314 | start() 315 | } 316 | } 317 | 318 | 319 | /** 320 | * Fly位置检测 如果已经超出屏幕则删除 321 | */ 322 | private fun checkFlyPosition(fly: Fly): Boolean { 323 | if (fly.flyType == FlyType.PLANE_BOSS) return true 324 | //如果view已经不再屏幕内了 删除它 325 | if (fly.x + fly.w <= -fly.w || 326 | 327 | fly.x >= w + fly.w || 328 | 329 | fly.y + fly.h <= -fly.h || 330 | 331 | fly.y >= h + fly.h 332 | ) { 333 | removeFly(fly) 334 | return false 335 | } 336 | return true 337 | } 338 | 339 | /** 340 | * 通过flyType来决定选择哪个集合中的元素与它进行碰撞检测 341 | */ 342 | private fun selectHitAndBeHit(fly: Fly) { 343 | when (fly.flyType) { 344 | FlyType.BULLET_GCD -> { 345 | //如果是我的子弹,则与敌机集合检测 346 | hitAndBeHit(fly, gmdPlanes) 347 | } 348 | FlyType.BULLET_GMD -> { 349 | //如果是敌机的子弹则与我机检测 350 | hitAndBeHit(fly, gcdPlanes) 351 | } 352 | FlyType.PLANE_GMD -> { 353 | //... 354 | hitAndBeHit(fly, gcdPlanes) 355 | } 356 | FlyType.PLANE_BOSS -> { 357 | //... 358 | hitAndBeHit(fly, gcdPlanes) 359 | } 360 | FlyType.BULLET_BOSS -> { 361 | //... 362 | hitAndBeHit(fly, gcdPlanes) 363 | } 364 | } 365 | } 366 | 367 | /** 368 | * 碰撞和被碰撞检测 369 | */ 370 | private fun hitAndBeHit(hitFly: Fly, beHitFlys: CopyOnWriteArrayList) { 371 | for (beHitFly in beHitFlys) { 372 | //碰撞之后跳出循环 373 | if (isCollision( 374 | hitFly.x, 375 | hitFly.y, 376 | hitFly.w.toFloat(), 377 | hitFly.h.toFloat(), 378 | beHitFly.x, 379 | beHitFly.y, 380 | beHitFly.w.toFloat(), 381 | beHitFly.h.toFloat() 382 | ) 383 | ) { 384 | sellingHP(hitFly, beHitFly) 385 | break 386 | } 387 | } 388 | } 389 | 390 | /** 391 | * 碰撞扣除HP 392 | */ 393 | private fun sellingHP(fly1: Fly, fly2: Fly) { 394 | fly1.HP -= fly2.power 395 | fly2.HP -= fly1.power 396 | isDid(fly1) 397 | isDid(fly2) 398 | } 399 | 400 | /** 401 | * 飞行物是否死亡 402 | */ 403 | private fun isDid(fly: Fly) { 404 | if (fly.HP <= 0) { 405 | createBoom(fly) 406 | removeFly(fly) 407 | if (fly.flyType == FlyType.PLANE_GMD || fly.flyType == FlyType.PLANE_BOSS) { 408 | onGameOverListener?.onKill(fly.flyType) 409 | } 410 | if (fly.flyType == FlyType.PLANE_GCD) { 411 | isGameOver = true 412 | onGameOverListener?.onGameOver() 413 | } 414 | } 415 | } 416 | 417 | 418 | /** 419 | * 碰撞检测 420 | */ 421 | private fun isCollision( 422 | x1: Float, 423 | y1: Float, 424 | w1: Float, 425 | h1: Float, 426 | x2: Float, 427 | y2: Float, 428 | w2: Float, 429 | h2: Float 430 | ): Boolean { 431 | if (x1 > x2 + w2 || x1 + w1 < x2 || y1 > y2 + h2 || y1 + h1 < y2) { 432 | return false 433 | } 434 | return true 435 | } 436 | 437 | 438 | /** 439 | * 手势拖动我的飞机 440 | */ 441 | @SuppressLint("ClickableViewAccessibility") 442 | private fun openFlyDrag(fly: Fly) { 443 | mapView.setOnTouchListener(object : View.OnTouchListener { 444 | var dx = 0 445 | var dy = 0 446 | var mx = 0 447 | var my = 0 448 | override fun onTouch(v: View?, event: MotionEvent?): Boolean { 449 | when (event?.action) { 450 | MotionEvent.ACTION_DOWN -> { 451 | dx = event.x.toInt() 452 | dy = event.y.toInt() 453 | } 454 | MotionEvent.ACTION_MOVE -> { 455 | mx = event.x.toInt() 456 | my = event.y.toInt() 457 | move() 458 | dx = mx 459 | dy = my 460 | } 461 | MotionEvent.ACTION_UP -> { 462 | 463 | } 464 | } 465 | return true 466 | } 467 | 468 | private fun move() { 469 | fly.x += (mx - dx) 470 | fly.y += (my - dy) 471 | 472 | //拖动超出屏幕检测 473 | if (fly.x < 0F) { 474 | fly.x = 0F 475 | } 476 | if (fly.y < 0F) { 477 | fly.y = 0F 478 | } 479 | 480 | if (fly.x + fly.w > w) { 481 | fly.x = w.toFloat() - fly.w 482 | } 483 | if (fly.y + fly.h > h) { 484 | fly.y = h.toFloat() - fly.h 485 | } 486 | } 487 | }) 488 | } 489 | 490 | 491 | /** 492 | * 添加Fly到集合和mapView 493 | */ 494 | private fun addFly(fly: Fly) { 495 | when (fly.flyType) { 496 | FlyType.BULLET_GCD -> { 497 | gcdBullets.add(fly) 498 | } 499 | FlyType.BULLET_GMD -> { 500 | gmdBullets.add(fly) 501 | } 502 | FlyType.BULLET_BOSS -> { 503 | gmdBullets.add(fly) 504 | } 505 | FlyType.PLANE_BOSS -> { 506 | gmdPlanes.add(fly) 507 | } 508 | FlyType.PLANE_GCD -> { 509 | gcdPlanes.add(fly) 510 | } 511 | FlyType.PLANE_GMD -> { 512 | gmdPlanes.add(fly) 513 | } 514 | } 515 | mapView.addFly(fly) 516 | } 517 | 518 | /** 519 | * 从集合和mapView中删除fly 520 | */ 521 | private fun removeFly(fly: Fly) { 522 | when (fly.flyType) { 523 | FlyType.BULLET_GCD -> { 524 | gcdBullets.remove(fly) 525 | } 526 | FlyType.BULLET_GMD -> { 527 | gmdBullets.remove(fly) 528 | } 529 | FlyType.PLANE_GMD -> { 530 | createPlane(FlyType.PLANE_GMD) 531 | gmdPlanes.remove(fly) 532 | } 533 | FlyType.PLANE_GCD -> { 534 | gcdPlanes.remove(fly) 535 | } 536 | FlyType.BULLET_BOSS -> { 537 | gmdBullets.remove(fly) 538 | } 539 | FlyType.PLANE_BOSS -> { 540 | startBossTimer() 541 | gmdPlanes.remove(fly) 542 | } 543 | } 544 | mapView.removeFly(fly) 545 | fly.boom() 546 | } 547 | 548 | fun quitGame() { 549 | isQuitGame = true 550 | } 551 | 552 | interface OnGameProgressListener { 553 | fun onGameOver() {} 554 | fun onKill(flyType: FlyType) {} 555 | fun onStart() {} 556 | fun onPause() {} 557 | } 558 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/cons/FlyType.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.cons 2 | 3 | 4 | enum class FlyType { 5 | PLANE_GCD, //我军飞机 6 | PLANE_GMD, //敌军飞机 7 | PLANE_BOSS, //boss飞机 8 | BULLET_GCD, //我军子弹 9 | BULLET_GMD, //敌军子弹 10 | BULLET_BOSS,//boss子弹 11 | BOOM, 12 | OTHER //预留 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/flyfactory/FlyFactory.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.flyfactory 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.view.View 6 | import com.jmj.planewars.fly.cons.FlyType 7 | import com.jmj.planewars.fly.flyobject.Boom 8 | import com.jmj.planewars.fly.flyobject.Bullet 9 | import com.jmj.planewars.fly.flyobject.Plane 10 | import com.jmj.planewars.fly.view.* 11 | import com.jmj.planewars.tools.dp2px 12 | import java.util.* 13 | 14 | object FlyFactory { 15 | private var random = Random() 16 | /** 17 | * 我的飞机 18 | */ 19 | private var gcdPlaneLength = 40 //飞机大小 20 | private var gcdBulletLength = 20 //子弹大小 21 | private var gcdPlaneHP = 10000 //飞机生命值 22 | private var gcdPlanePower = 100 //飞机撞击威力 23 | private var gcdBulletHP = 0 //子弹生命值 24 | private var gcdBulletPower = 100 //子弹威力 25 | 26 | /** 27 | * 敌机 28 | */ 29 | private var gmdPlaneLength = 30 //飞机大小 30 | private var gmdBulletLength = 20 //子弹大小 31 | private var gmdPlaneHp = 100 //飞机生命值 32 | private var gmdPlanePower = 100 //飞机撞击威力 33 | private var gmdBulletHP = 0 //子弹生命值 34 | private var gmdBulletPower = 100 //子弹威力 35 | 36 | /** 37 | * BOSS 38 | */ 39 | private var bossPlaneLength = 150 //飞机大小 40 | private var bossBulletLength = 60 //子弹大小 41 | private var bossPlaneHp = 5000 //飞机生命值 42 | private var bossPlanePower = 100 //飞机撞击威力 43 | private var bossBulletHP = 0 //子弹生命值 44 | private var bossBulletPower = 100 //子弹威力 45 | 46 | 47 | private var GMD_PLANE_WING = Color.parseColor("#42AFF5") 48 | private var GMD_PLANE_BODY = Color.parseColor("#F06292") 49 | private var GMD_BULLET = GMD_PLANE_BODY 50 | 51 | private var GCD_PLANE_WING = Color.parseColor("#FFC107") 52 | private var GCD_PLANE_BODY = Color.parseColor("#009688") 53 | private var GCD_BULLET = GCD_PLANE_WING 54 | 55 | private var BOSS_PLANE_WING = Color.parseColor("#FF5722") 56 | private var BOSS_PLANE_BODY = Color.parseColor("#00BCD4") 57 | private var BOSS_BULLET = BOSS_PLANE_WING 58 | 59 | /** 60 | * 获取飞机的Fly 61 | */ 62 | fun getPlane(context: Context, flyType: FlyType): Plane? { 63 | return when (flyType) { 64 | FlyType.PLANE_GCD -> { 65 | Plane( 66 | getView(context, flyType)!!, 67 | flyType, 68 | dp2px(gcdPlaneLength), 69 | dp2px(gcdPlaneLength), 70 | 1, 71 | gcdPlanePower, 72 | gcdPlaneHP 73 | ) 74 | } 75 | FlyType.PLANE_GMD -> { 76 | Plane( 77 | getView(context, flyType)!!, 78 | flyType, 79 | dp2px(gmdPlaneLength), 80 | dp2px(gmdPlaneLength), 81 | random.nextInt(3) + 3, 82 | gmdPlanePower, 83 | gmdPlaneHp 84 | ) 85 | } 86 | FlyType.PLANE_BOSS -> { 87 | Plane( 88 | getView(context, flyType)!!, 89 | flyType, 90 | dp2px(bossPlaneLength), 91 | dp2px(bossPlaneLength), 92 | 2, 93 | bossPlanePower, 94 | bossPlaneHp 95 | ) 96 | } 97 | else -> null 98 | } 99 | } 100 | 101 | /** 102 | * 获取子弹的Fly 103 | */ 104 | fun getBullet(context: Context, flyType: FlyType): Bullet? { 105 | return when (flyType) { 106 | FlyType.PLANE_GCD -> { 107 | Bullet( 108 | getView(context, FlyType.BULLET_GCD)!!, 109 | FlyType.BULLET_GCD, 110 | dp2px(gcdBulletLength), 111 | dp2px(gcdBulletLength), 112 | 1, 113 | gcdBulletPower, 114 | gcdBulletHP 115 | ) 116 | } 117 | FlyType.PLANE_GMD -> { 118 | Bullet( 119 | getView(context, FlyType.BULLET_GMD)!!, 120 | FlyType.BULLET_GMD, 121 | dp2px(gmdBulletLength), 122 | dp2px(gmdBulletLength), 123 | random.nextInt(3), 124 | gmdBulletPower, 125 | gmdBulletHP 126 | ) 127 | } 128 | FlyType.PLANE_BOSS -> { 129 | Bullet( 130 | getView(context, FlyType.BULLET_BOSS)!!, 131 | FlyType.BULLET_BOSS, 132 | dp2px(bossBulletLength), 133 | dp2px(bossBulletLength), 134 | 1, 135 | bossBulletPower, 136 | bossBulletHP 137 | ) 138 | } 139 | else -> { 140 | null 141 | } 142 | } 143 | } 144 | 145 | /** 146 | * 获取爆炸效果的Fly 147 | */ 148 | fun getBoom(context: Context, flyType: FlyType): Boom? { 149 | return when (flyType) { 150 | FlyType.PLANE_GMD -> { 151 | Boom( 152 | getBoomView(context, flyType)!!, 153 | FlyType.BOOM, 154 | dp2px(gmdPlaneLength), 155 | dp2px(gmdPlaneLength), 156 | 0 157 | ) 158 | } 159 | FlyType.BULLET_GMD -> { 160 | Boom( 161 | getBoomView(context, flyType)!!, 162 | FlyType.BOOM, 163 | dp2px(gmdBulletLength), 164 | dp2px(gmdBulletLength), 165 | 0 166 | ) 167 | } 168 | 169 | FlyType.PLANE_GCD -> { 170 | Boom( 171 | getBoomView(context, flyType)!!, 172 | FlyType.BOOM, 173 | dp2px(gcdPlaneLength), 174 | dp2px(gcdBulletLength), 175 | 0 176 | ) 177 | } 178 | 179 | 180 | FlyType.BULLET_GCD -> { 181 | Boom( 182 | getBoomView(context, flyType)!!, 183 | FlyType.BOOM, 184 | dp2px(gmdBulletLength), 185 | dp2px(gmdBulletLength), 186 | 0 187 | ) 188 | } 189 | 190 | FlyType.PLANE_BOSS -> { 191 | Boom( 192 | getBoomView(context, flyType)!!, 193 | FlyType.BOOM, 194 | dp2px(bossPlaneLength), 195 | dp2px(bossBulletLength), 196 | 0 197 | ) 198 | } 199 | 200 | FlyType.BULLET_BOSS -> { 201 | Boom( 202 | getBoomView(context, flyType)!!, 203 | FlyType.BOOM, 204 | dp2px(bossBulletLength), 205 | dp2px(bossBulletLength), 206 | 0 207 | ) 208 | } 209 | 210 | else -> { 211 | null 212 | } 213 | 214 | } 215 | } 216 | 217 | /** 218 | * 获取爆炸效果的View 219 | */ 220 | private fun getBoomView(context: Context, flyType: FlyType): View? { 221 | return when (flyType) { 222 | FlyType.PLANE_GMD -> { 223 | FlyBoomView(context, GMD_PLANE_WING) 224 | } 225 | FlyType.BULLET_GMD -> { 226 | FlyBoomView(context, GMD_BULLET) 227 | } 228 | FlyType.PLANE_GCD -> { 229 | FlyBoomView(context, GCD_PLANE_WING) 230 | } 231 | FlyType.BULLET_GCD -> { 232 | FlyBoomView(context, GCD_BULLET) 233 | } 234 | FlyType.PLANE_BOSS -> { 235 | FlyBoomView(context, BOSS_PLANE_WING) 236 | } 237 | FlyType.BULLET_BOSS -> { 238 | FlyBoomView(context, BOSS_BULLET) 239 | } 240 | else -> null 241 | } 242 | } 243 | 244 | /** 245 | * 获取飞机或子弹的View 246 | */ 247 | private fun getView(context: Context, flyType: FlyType): View? { 248 | return when (flyType) { 249 | FlyType.PLANE_GCD -> { 250 | PlaneView(context, GCD_PLANE_WING, GCD_PLANE_BODY, false) 251 | } 252 | FlyType.PLANE_BOSS -> { 253 | PlaneView(context, BOSS_PLANE_WING, BOSS_PLANE_BODY, true) 254 | } 255 | FlyType.PLANE_GMD -> { 256 | PlaneView(context, GMD_PLANE_WING, GMD_PLANE_BODY, true) 257 | } 258 | FlyType.BULLET_GCD -> { 259 | BulletView(context, GCD_BULLET, false) 260 | } 261 | FlyType.BULLET_GMD -> { 262 | BulletView(context, GMD_BULLET, true) 263 | } 264 | FlyType.BULLET_BOSS -> { 265 | BulletView(context, BOSS_BULLET, true) 266 | } 267 | else -> null 268 | } 269 | } 270 | 271 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/flyobject/Boom.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.flyobject 2 | 3 | import android.view.View 4 | import com.jmj.planewars.fly.cons.FlyType 5 | 6 | class Boom : Fly { 7 | constructor(view: View, flyType: FlyType, w: Int, h: Int, speed: Int) : super( 8 | view, 9 | flyType, 10 | w, 11 | h, 12 | speed, 13 | 0, 14 | 0 15 | ) 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/flyobject/Bullet.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.flyobject 2 | 3 | import android.view.View 4 | import com.jmj.planewars.fly.cons.FlyType 5 | import com.jmj.planewars.fly.flyobject.Fly 6 | 7 | 8 | class Bullet : Fly { 9 | 10 | var bulletColor = 0 11 | 12 | constructor( 13 | view: View, 14 | flyType: FlyType, 15 | w: Int, 16 | h: Int, 17 | speed: Int, 18 | power: Int, 19 | HP: Int 20 | ) : super(view, flyType, w, h, speed, power, HP) 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/flyobject/Fly.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.flyobject 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import com.jmj.planewars.fly.cons.FlyType 7 | 8 | abstract class Fly { 9 | //飞行器类型 10 | var flyType: FlyType 11 | //飞行器展示的view 12 | var view: View 13 | //飞行器的的偏移值 xy 14 | var x = 0F 15 | set(value) { 16 | view.x = value 17 | cx = (view.x + w / 2) 18 | field = value 19 | } 20 | var y = 0F 21 | set(value) { 22 | view.y = value 23 | cy = (view.y + h / 2) 24 | field = value 25 | } 26 | //飞行器的宽高 27 | var w = 0 28 | var h = 0 29 | //飞行器的血量 30 | var HP = 100 31 | //飞行器的碰撞威力 32 | var power = 100 33 | //这个飞行器是否已经死亡 34 | var isBoom = false 35 | //飞行器的移动速度 越小越快 36 | var speed = 1 37 | //上下文 38 | var context: Context 39 | //飞行器的中心点 40 | var cx = 0F 41 | var cy = 0F 42 | 43 | 44 | 45 | 46 | constructor(view: View, flyType: FlyType, w: Int, h: Int, speed: Int, power: Int, HP: Int) { 47 | this.view = view 48 | this.flyType = flyType 49 | this.speed = speed 50 | this.w = w 51 | this.h = h 52 | this.power = power 53 | this.HP = HP 54 | this.context = view.context 55 | val layoutParams = ViewGroup.LayoutParams(w, h) 56 | view.layoutParams = layoutParams 57 | } 58 | 59 | fun setFlyPosition(x: Int, y: Int) { 60 | } 61 | 62 | /** 63 | * 死亡 64 | */ 65 | fun boom() { 66 | isBoom = true 67 | } 68 | 69 | 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/flyobject/Plane.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.flyobject 2 | 3 | import android.view.View 4 | import com.jmj.planewars.fly.cons.FlyType 5 | 6 | class Plane : Fly { 7 | 8 | 9 | constructor( 10 | view: View, 11 | flyType: FlyType, 12 | w: Int, 13 | h: Int, 14 | speed: Int, 15 | power: Int, 16 | HP: Int 17 | ) : super(view, flyType, w, h, speed, power, HP) 18 | 19 | 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/view/BaseView.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | 7 | open class BaseView :View{ 8 | var w = 0F 9 | var h = 0F 10 | var cx = 0F 11 | var cy = 0F 12 | 13 | constructor(context: Context?) : super(context) 14 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 15 | 16 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 17 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 18 | w = measuredWidth.toFloat() 19 | h = measuredHeight.toFloat() 20 | cx = w / 2 21 | cy = h / 2 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/view/BulletView.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.view 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.util.AttributeSet 6 | import androidx.annotation.ColorInt 7 | 8 | /** 9 | * 子弹 10 | */ 11 | class BulletView : FireView { 12 | 13 | /** 14 | * 子弹颜色 15 | */ 16 | private var bulletColor = 0 17 | 18 | override fun onDraw(canvas: Canvas?) { 19 | super.onDraw(canvas) 20 | canvas?.let { 21 | paint.color = bulletColor 22 | 23 | it.drawRoundRect( 24 | horizontalMolecule * 2, 25 | 0F, 26 | horizontalMolecule * 3, 27 | verticalMolecule * 4, 28 | 10F, 29 | 10F, 30 | paint 31 | ) 32 | 33 | 34 | it.drawRoundRect( 35 | horizontalMolecule, 36 | verticalMolecule * 4 - verticalMolecule, 37 | w - horizontalMolecule, 38 | verticalMolecule * 4, 39 | 10F, 40 | 10F, 41 | paint 42 | ) 43 | } 44 | } 45 | 46 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 47 | constructor(context: Context?, @ColorInt bulletColor: Int, isReverse: Boolean) : super( 48 | context, 49 | isReverse 50 | ) { 51 | this.bulletColor = bulletColor 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/view/FireView.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.view 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Color 6 | import android.graphics.Paint 7 | import android.graphics.Rect 8 | import android.util.AttributeSet 9 | import com.jmj.planewars.fly.viewtools.FlyFire 10 | import com.jmj.planewars.tools.dp2px 11 | 12 | /** 13 | * 有火焰效果的基类View 14 | */ 15 | abstract class FireView : BaseView { 16 | private var flyFire: FlyFire? = null 17 | private var isReverse = false 18 | var horizontalMolecule = 0F 19 | var verticalMolecule = 0F 20 | var paint = Paint().apply { 21 | isAntiAlias = true 22 | strokeWidth = dp2px(2).toFloat() 23 | color = Color.BLACK 24 | style = Paint.Style.STROKE 25 | } 26 | 27 | 28 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 29 | 30 | constructor(context: Context?, isReverse: Boolean) : super(context) { 31 | this.isReverse = isReverse 32 | } 33 | 34 | 35 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 36 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 37 | computeRect() 38 | elevation = dp2px(10).toFloat() 39 | } 40 | 41 | 42 | override fun onDraw(canvas: Canvas?) { 43 | super.onDraw(canvas) 44 | canvas?.let { 45 | if (isReverse) { 46 | canvas.rotate(180F, w / 2, h / 2) 47 | } 48 | flyFire?.drawParticle(it) 49 | } 50 | } 51 | 52 | 53 | /** 54 | * 计算每块区域的具体位置 55 | */ 56 | private fun computeRect() { 57 | var horizontalDenominator = 5 58 | var verticalDenominator = 5 59 | 60 | //横向每份的长度 61 | horizontalMolecule = w / horizontalDenominator 62 | //竖向每份的长度 63 | verticalMolecule = h / verticalDenominator 64 | 65 | var rect = Rect( 66 | (horizontalMolecule * 2).toInt(), 67 | (verticalMolecule * 4).toInt(), 68 | (horizontalMolecule * 3).toInt(), 69 | h.toInt() 70 | ) 71 | flyFire = FlyFire(rect, this) 72 | } 73 | 74 | 75 | override fun onDetachedFromWindow() { 76 | super.onDetachedFromWindow() 77 | flyFire?.stopDrawParticle() 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/view/FlyBoomView.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.view 2 | 3 | import android.animation.ValueAnimator 4 | import android.content.Context 5 | import android.graphics.Canvas 6 | import android.graphics.Color 7 | import android.graphics.Paint 8 | import android.graphics.Rect 9 | import android.util.AttributeSet 10 | import android.view.animation.LinearInterpolator 11 | import androidx.annotation.ColorInt 12 | import androidx.core.animation.doOnEnd 13 | import com.jmj.planewars.tools.dp2px 14 | import kotlin.math.pow 15 | import kotlin.math.sqrt 16 | 17 | /** 18 | * 爆炸效果 19 | */ 20 | class FlyBoomView : BaseView { 21 | /** 22 | * 方块集合 23 | */ 24 | private var rects = ArrayList() 25 | /** 26 | * 爆炸动画时间 27 | */ 28 | private var BOOM_DURATION = 500L 29 | /** 30 | * 爆炸动画 31 | */ 32 | var animatorListener: AnimatorListener? = null 33 | 34 | private var paint = Paint().apply { 35 | isAntiAlias = true 36 | strokeWidth = dp2px(2).toFloat() 37 | color = Color.RED 38 | style = Paint.Style.FILL 39 | } 40 | 41 | constructor(context: Context?, @ColorInt boomColor: Int ) : super(context) { 42 | paint.color = boomColor 43 | } 44 | 45 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 46 | 47 | 48 | /** 49 | * 所有的方块实际都朝一个方向移动,但是每移动一个旋转30°,造成圆形扩散效果 50 | */ 51 | override fun onDraw(canvas: Canvas?) { 52 | super.onDraw(canvas) 53 | for (index in rects.indices) { 54 | canvas!!.drawRect(rects[index], paint) 55 | canvas.rotate(30F, cx, cy) 56 | } 57 | 58 | } 59 | 60 | 61 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 62 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 63 | w = measuredWidth.toFloat() 64 | h = measuredHeight.toFloat() 65 | cx = w / 2 66 | cy = h / 2 67 | computeRect() 68 | boomAnim() 69 | } 70 | 71 | /** 72 | * 移动方块动画 73 | */ 74 | private fun boomAnim() { 75 | val animEnd = (sqrt(w.coerceAtLeast(h).toDouble().pow(2.0)) / 2).toInt() 76 | var oldAnimatedValue = 0 77 | ValueAnimator.ofInt(0, (animEnd * 1.5F).toInt()).apply { 78 | addUpdateListener { 79 | 80 | val animatedValue = it.animatedValue as Int 81 | paint.alpha = (255 - (animatedValue * (255F / (animEnd * 1.5F)))).toInt() 82 | 83 | for (index in rects.indices) { 84 | val rect = rects[index] 85 | rect.left += animatedValue - oldAnimatedValue 86 | rect.right += animatedValue - oldAnimatedValue 87 | rect.top += animatedValue - oldAnimatedValue 88 | rect.bottom += animatedValue - oldAnimatedValue 89 | } 90 | oldAnimatedValue = animatedValue 91 | invalidate() 92 | } 93 | interpolator = LinearInterpolator() 94 | duration = BOOM_DURATION 95 | doOnEnd { 96 | animatorListener?.onAnimationEnd() 97 | } 98 | start() 99 | } 100 | } 101 | 102 | /** 103 | * 添加12个方块在View的中心 104 | */ 105 | private fun computeRect() { 106 | rects.clear() 107 | if (rects.size == 0) { 108 | var length = w.coerceAtMost(h) / 5 109 | for (i in 0..11) { 110 | rects.add( 111 | Rect( 112 | (cx - length / 2).toInt(), 113 | (cy - length / 2).toInt(), 114 | (cx + length / 2).toInt(), 115 | (cy + length / 2).toInt() 116 | ) 117 | ) 118 | } 119 | } 120 | } 121 | 122 | interface AnimatorListener { 123 | fun onAnimationEnd() 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/view/MapView.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.ViewGroup 6 | import com.jmj.planewars.fly.flyobject.Fly 7 | 8 | /** 9 | * 飞行物的容器 10 | */ 11 | class MapView : ViewGroup { 12 | constructor(context: Context?) : super(context) 13 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 14 | 15 | private var w = 0 16 | private var h = 0 17 | private var loadFinish = false 18 | var onMeasureFinishListener: OnViewLoadFinishListener? = null 19 | 20 | init { 21 | setWillNotDraw(true) 22 | } 23 | 24 | /** 25 | * 指定飞行物的位置 26 | */ 27 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 28 | for (i in 0 until childCount) { 29 | val child = getChildAt(i) 30 | if (child != null) { 31 | val childW = child.measuredWidth 32 | val childH = child.measuredHeight 33 | child.layout(0, 0, childW, childH) 34 | } 35 | 36 | } 37 | } 38 | 39 | /** 40 | * 测量 41 | */ 42 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 43 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 44 | measureChildren(widthMeasureSpec, heightMeasureSpec) 45 | w = measuredWidth 46 | h = measuredHeight 47 | if (!loadFinish) { 48 | onMeasureFinishListener?.onFinish(w, h) 49 | loadFinish = true 50 | } 51 | } 52 | 53 | /** 54 | * 添加飞行物 55 | */ 56 | fun addFly(fly: Fly) { 57 | addView(fly.view) 58 | 59 | } 60 | 61 | /** 62 | * 删除飞行物 63 | */ 64 | fun removeFly(fly: Fly) { 65 | removeView(fly.view) 66 | } 67 | 68 | interface OnViewLoadFinishListener { 69 | fun onFinish(w: Int, h: Int) 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/view/PlaneView.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.view 2 | 3 | import android.animation.ValueAnimator 4 | import android.content.Context 5 | import android.graphics.Canvas 6 | import android.graphics.Color 7 | import android.graphics.Paint 8 | import androidx.annotation.ColorInt 9 | 10 | /** 11 | * 飞机 12 | */ 13 | class PlaneView : FireView { 14 | /** 15 | * 遮罩颜色 16 | */ 17 | private var maskColor = 0 18 | /** 19 | * 翅膀颜色 20 | */ 21 | private var wingColor = 0 22 | /** 23 | * 躯干颜色 24 | */ 25 | private var bodyColor = 0 26 | 27 | private var maskAnim: ValueAnimator? = null 28 | 29 | constructor( 30 | context: Context?, @ColorInt wingColor: Int, @ColorInt bodyColor: Int, 31 | isReverse: Boolean 32 | ) : super( 33 | context, 34 | isReverse 35 | ) { 36 | this.wingColor = wingColor 37 | this.bodyColor = bodyColor 38 | } 39 | 40 | 41 | init { 42 | paint.style = Paint.Style.FILL 43 | } 44 | 45 | override fun onDraw(canvas: Canvas?) { 46 | super.onDraw(canvas) 47 | canvas?.let { 48 | paint.color = bodyColor 49 | 50 | it.drawRoundRect( 51 | horizontalMolecule * 2, 52 | 0F, 53 | horizontalMolecule * 3, 54 | verticalMolecule * 4, 55 | 10F, 56 | 10F, 57 | paint 58 | ) 59 | paint.color = wingColor 60 | 61 | it.drawRoundRect( 62 | 0F, 63 | verticalMolecule / 2, 64 | w, 65 | verticalMolecule + verticalMolecule / 2, 66 | 10F, 67 | 10F, 68 | paint 69 | ) 70 | 71 | it.drawRoundRect( 72 | horizontalMolecule, 73 | verticalMolecule * 4 - verticalMolecule, 74 | w - horizontalMolecule, 75 | verticalMolecule * 4, 76 | 10F, 77 | 10F, 78 | paint 79 | ) 80 | 81 | if (maskAnim != null) { 82 | paint.color = maskColor 83 | it.drawCircle(cx, cy, w.coerceAtMost(h) / 2 * 1.5F, paint) 84 | paint.alpha = 0xFF 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * 开启遮罩 91 | */ 92 | fun openMask() { 93 | if (maskAnim == null) { 94 | maskAnim = 95 | ValueAnimator.ofArgb(Color.parseColor("#55FFFFFF"), Color.parseColor("#11FFFFFF")) 96 | .apply { 97 | addUpdateListener { 98 | maskColor = it.animatedValue as Int 99 | invalidate() 100 | } 101 | repeatCount = ValueAnimator.INFINITE 102 | repeatMode = ValueAnimator.REVERSE 103 | duration = 1000 104 | start() 105 | } 106 | } 107 | 108 | } 109 | 110 | /** 111 | * 关闭遮罩 112 | */ 113 | fun closeMask() { 114 | maskAnim?.cancel() 115 | maskAnim = null 116 | } 117 | 118 | override fun onDetachedFromWindow() { 119 | super.onDetachedFromWindow() 120 | closeMask() 121 | } 122 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/view/ScrollStarsView.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.view 2 | 3 | import android.animation.ValueAnimator 4 | import android.content.Context 5 | import android.graphics.Canvas 6 | import android.graphics.Color 7 | import android.graphics.Paint 8 | import android.util.AttributeSet 9 | import android.view.animation.LinearInterpolator 10 | import java.util.* 11 | import java.util.concurrent.CopyOnWriteArrayList 12 | 13 | /** 14 | * 滚动的星空背景 15 | */ 16 | class ScrollStarsView : BaseView { 17 | /** 18 | * 圆形集合 19 | */ 20 | private var circleStars = CopyOnWriteArrayList() 21 | /** 22 | * 圆形移动动画 23 | */ 24 | private var moveRectAnim: ValueAnimator? = null 25 | 26 | private var random = Random() 27 | /** 28 | * 圆形直径 29 | */ 30 | private var diameter = 0 31 | 32 | private var paint = Paint().apply { 33 | isAntiAlias = true 34 | style = Paint.Style.FILL 35 | 36 | } 37 | 38 | constructor(context: Context?) : super(context) 39 | constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 40 | 41 | 42 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 43 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 44 | diameter = (w / 3).toInt() 45 | addRect() 46 | moveRect() 47 | } 48 | 49 | /** 50 | * 添加圆形 51 | */ 52 | private fun addRect() { 53 | circleStars.add( 54 | CircleStar( 55 | (random.nextInt(4) * diameter).toFloat(), 56 | -diameter.toFloat(), 57 | diameter / 2F * (random.nextFloat() + 0.1F) 58 | ) 59 | ) 60 | } 61 | 62 | override fun onDraw(canvas: Canvas?) { 63 | super.onDraw(canvas) 64 | canvas?.let { c -> 65 | /** 66 | * 绘制背景矩形 67 | */ 68 | paint.color = Color.parseColor("#0F2332") 69 | c.drawRect(0F, 0F, w, h, paint) 70 | 71 | /** 72 | * 绘制圆形 73 | */ 74 | paint.color = Color.parseColor("#374060") 75 | circleStars.forEach { 76 | c.drawCircle(it.x, it.y, it.r, paint) 77 | } 78 | } 79 | } 80 | 81 | 82 | /** 83 | * 移动圆形 每走diameter*2的距离 触发一次添加 84 | * 85 | */ 86 | private fun moveRect() { 87 | if (moveRectAnim == null) { 88 | var oldAnimateValue = 0 89 | moveRectAnim = ValueAnimator.ofInt(1, diameter * 2) 90 | .apply { 91 | addUpdateListener { it -> 92 | val animateValue = it.animatedValue as Int 93 | 94 | //repeat的时候重置oldAnimateValue 95 | if (oldAnimateValue > animateValue) { 96 | oldAnimateValue = 0 97 | addRect() 98 | } 99 | 100 | circleStars.forEach { 101 | it.y += animateValue - oldAnimateValue 102 | //如果已经走出了屏幕外则删除它 103 | if (it.y > h + diameter) { 104 | circleStars.remove(it) 105 | } 106 | } 107 | 108 | oldAnimateValue = animateValue 109 | invalidate() 110 | } 111 | 112 | repeatCount = ValueAnimator.INFINITE 113 | interpolator = LinearInterpolator() 114 | duration = 2000 115 | start() 116 | } 117 | } 118 | } 119 | 120 | override fun onDetachedFromWindow() { 121 | super.onDetachedFromWindow() 122 | moveRectAnim?.cancel() 123 | moveRectAnim = null 124 | } 125 | 126 | data class CircleStar(var x: Float, var y: Float, var r: Float) 127 | 128 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/fly/viewtools/FlyFire.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.fly.viewtools 2 | 3 | import android.animation.ValueAnimator 4 | import android.graphics.* 5 | import android.view.View 6 | import java.util.concurrent.CopyOnWriteArrayList 7 | 8 | class FlyFire(private var rect: Rect, private var view: View) { 9 | private val particles = CopyOnWriteArrayList() 10 | private var borderWidth = 0 11 | private var paint = Paint() 12 | private var fireAnim: ValueAnimator? = null 13 | 14 | init { 15 | paint.color = Color.LTGRAY 16 | } 17 | 18 | /** 19 | * 绘制粒子 20 | */ 21 | private fun draw(canvas: Canvas) { 22 | if (fireAnim == null) { 23 | fireAnim = ValueAnimator.ofFloat(0F, 1F) 24 | .apply { 25 | addUpdateListener { 26 | view.invalidate() 27 | } 28 | repeatCount = ValueAnimator.INFINITE 29 | start() 30 | } 31 | } 32 | 33 | particles.forEach { 34 | var h = rect.bottom - rect.top 35 | var yProgress = it.y - rect.top 36 | //粒子透明度 37 | paint.alpha = (255 - yProgress * (255F / h)).toInt() 38 | canvas.drawRect( 39 | it.x, 40 | it.y, 41 | it.x + borderWidth, 42 | it.y + borderWidth, 43 | paint 44 | ) 45 | } 46 | } 47 | 48 | /** 49 | *计算粒子 50 | */ 51 | fun drawParticle(canvas: Canvas) { 52 | borderWidth = (rect.right - rect.left) / 3 53 | if (borderWidth < 1) { 54 | borderWidth = 1 55 | } 56 | 57 | if (particles.size == 0) { 58 | addParticle((rect.right - rect.left) / borderWidth / 2 * 2) 59 | } 60 | //改变位置 61 | particles.forEach { 62 | if (it.xv > 0) { 63 | it.xv += 0.005f 64 | } else { 65 | it.xv += -0.005f 66 | } 67 | it.yv = it.yv + 0.2F 68 | 69 | it.x = it.x + it.xv 70 | it.y = it.y + it.yv 71 | } 72 | 73 | particles.forEach { 74 | if (it.y > rect.bottom) { 75 | particles.remove(it) 76 | } 77 | } 78 | 79 | //每绘制一次添加一排粒子 80 | addParticle((rect.right - rect.left) / borderWidth / 2 * 2) 81 | draw(canvas) 82 | } 83 | 84 | /** 85 | * 添加粒子 86 | */ 87 | private fun addParticle(count: Int) { 88 | for (i in 0 until count) { 89 | val molecule = (rect.right - rect.left) 90 | 91 | val particle = 92 | Particle( 93 | rect.left + molecule * Math.random().toFloat() - borderWidth / 2, 94 | rect.top.toFloat() 95 | ) 96 | particle.xv = 0.5f - 1 * Math.random().toFloat() 97 | particles.add(particle) 98 | } 99 | } 100 | 101 | fun stopDrawParticle() { 102 | fireAnim?.cancel() 103 | } 104 | 105 | data class Particle(var x: Float, var y: Float, var xv: Float = 1F, var yv: Float = 1F) 106 | 107 | 108 | } 109 | 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/tools/ExpansionAny.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.tools 2 | 3 | import android.os.Environment 4 | import android.util.Log 5 | import com.jmj.planewars.App 6 | import com.jmj.planewars.BuildConfig 7 | import java.io.File 8 | 9 | 10 | /** 11 | * 日志打印 12 | */ 13 | fun Any.myLog(msg: String, logType: String = "e") { 14 | if (!BuildConfig.DEBUG) { 15 | return 16 | } 17 | val stacks = Throwable().stackTrace 18 | val className = this::class.java.simpleName 19 | val methodName = stacks[1].methodName 20 | val lineNumber = stacks[1].lineNumber 21 | 22 | var logString = "MyLog:($className::$methodName::$lineNumber)" 23 | 24 | when (logType) { 25 | "i" -> { 26 | Log.i(logString, msg) 27 | } 28 | "d" -> { 29 | Log.d(logString, msg) 30 | } 31 | "v" -> { 32 | Log.v(logString, msg) 33 | } 34 | "w" -> { 35 | Log.w(logString, msg) 36 | } 37 | "e" -> { 38 | Log.e(logString, msg) 39 | } 40 | } 41 | 42 | } 43 | 44 | fun Any.myLog(vararg msg: Any) { 45 | if (!BuildConfig.DEBUG) { 46 | return 47 | } 48 | var s = "" 49 | msg.forEach { 50 | s += "$it," 51 | } 52 | myLog(s) 53 | } 54 | 55 | 56 | fun Any.dp2px(dp: Int): Int { 57 | var scale = App.getInstance().resources.displayMetrics.density 58 | return return ((dp * scale + 0.5f).toInt()) 59 | } 60 | 61 | fun Any.px2dp(px: Int): Int { 62 | var scale = App.getInstance().resources.displayMetrics.density 63 | return return ((px / scale + 0.5f).toInt()) 64 | } 65 | 66 | /** 67 | * 创建文件路径 68 | */ 69 | fun Any.createFile(fileName: String): File { 70 | var dir = 71 | File(Environment.getExternalStorageDirectory(), "Feijidazhan") 72 | if (!dir.exists()) { 73 | dir.mkdirs() 74 | } 75 | return File(dir, fileName) 76 | } 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/jmj/planewars/tools/ExpansionContext.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars.tools 2 | 3 | import android.content.Context 4 | import android.widget.Toast 5 | import androidx.annotation.StringRes 6 | 7 | 8 | var SHARE_FILE_NAME="" 9 | 10 | fun Context.showToast(msg: String) { 11 | if (msg.isNotEmpty()) { 12 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() 13 | } 14 | } 15 | 16 | fun Context.showToast(@StringRes msg: Int) { 17 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() 18 | } 19 | 20 | /** 21 | * SharedPreferences 22 | */ 23 | fun Context.putStringToShared(key: String, value: String?) { 24 | getSharedPreferences(SHARE_FILE_NAME, Context.MODE_PRIVATE) 25 | .edit() 26 | .putString(key, value) 27 | .apply() 28 | } 29 | 30 | fun Context.getStringByShared(key: String): String? { 31 | return getSharedPreferences(SHARE_FILE_NAME, Context.MODE_PRIVATE) 32 | .getString(key, "") 33 | } 34 | 35 | fun Context.putIntToShared(key: String, value: Int) { 36 | getSharedPreferences(SHARE_FILE_NAME, Context.MODE_PRIVATE) 37 | .edit() 38 | .putInt(key, value) 39 | .apply() 40 | } 41 | 42 | fun Context.getIntByShared(key: String): Int { 43 | return getSharedPreferences(SHARE_FILE_NAME, Context.MODE_PRIVATE) 44 | .getInt(key, -1) 45 | } 46 | 47 | fun Context.putBooleanToShared(key: String, value: Boolean) { 48 | getSharedPreferences(SHARE_FILE_NAME, Context.MODE_PRIVATE) 49 | .edit() 50 | .putBoolean(key, value) 51 | .apply() 52 | } 53 | 54 | fun Context.getBooleanByShared(key: String): Boolean { 55 | return getSharedPreferences(SHARE_FILE_NAME, Context.MODE_PRIVATE) 56 | .getBoolean(key, false) 57 | } 58 | 59 | 60 | 61 | fun Context.dp2px(dp: Int): Int { 62 | var scale = this.resources.displayMetrics.density 63 | return return ((dp * scale + 0.5f).toInt()) 64 | } 65 | 66 | fun Context.px2dp(px: Int): Int { 67 | var scale = this.resources.displayMetrics.density 68 | return return ((px / scale + 0.5f).toInt()) 69 | } 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/plane.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 21 | 22 | 23 | 36 | 37 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #0F2332 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PlaneWars 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/jmj/planewars/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.jmj.planewars 2 | 3 | import android.graphics.Rect 4 | import org.junit.Test 5 | 6 | import org.junit.Assert.* 7 | import kotlin.math.sqrt 8 | import kotlin.random.Random 9 | 10 | /** 11 | * Example local unit test, which will execute on the development machine (host). 12 | * 13 | * See [testing documentation](http://d.android.com/tools/testing). 14 | */ 15 | class ExampleUnitTest { 16 | @Test 17 | fun addition_isCorrect() { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.50' 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.5.0' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | maven { url 'https://jitpack.io' } 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /gif/3A0D5009E4438B045A19CA0ACC6754BF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/gif/3A0D5009E4438B045A19CA0ACC6754BF.gif -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiangMingJieJie/PlaneWars/25f36edd637a64d29c17b135640192ffc172cff7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Feb 23 15:34:29 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name='PlaneWars' 3 | --------------------------------------------------------------------------------