1. 项目概述
本投篮小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含完整的物理引擎模拟和计分系统。
2. 项目结构
/src/main/java/com/example/basketball/├── MainAbilitySlice.java // 主界面├── GameView.java // 游戏核心逻辑├── Basketball.java // 篮球类├── Hoop.java // 篮筐类└── resources/├── base/│ ├── layout/│ │ └── ability_main.xml // 布局文件│ └── media/│ ├── basketball.png // 篮球图片│ ├── hoop.png // 篮筐图片│ └── background.jpg // 背景图片
3. 核心代码实现
3.1 篮球类(Basketball.java)
package com.example.basketball;import ohos.agp.utils.Point;public class Basketball {private Point position; // 球的位置private Point velocity; // 球的速度private int radius; // 球的半径private boolean isMoving; // 是否在移动public Basketball(int x, int y, int radius) {this.position = new Point(x, y);this.velocity = new Point(0, 0);this.radius = radius;this.isMoving = false;}// getter和setter方法public Point getPosition() { return position; }public Point getVelocity() { return velocity; }public int getRadius() { return radius; }public boolean isMoving() { return isMoving; }public void setPosition(int x, int y) {position.modify(x, y);}public void setVelocity(int x, int y) {velocity.modify(x, y);isMoving = x != 0 || y != 0;}// 更新球的位置public void update() {if (!isMoving) return;// 更新位置position.modify(position.getPointX() + velocity.getPointX(), position.getPointY() + velocity.getPointY());// 模拟重力velocity.modify(velocity.getPointX(), velocity.getPointY() + 2);// 模拟空气阻力if (velocity.getPointX() > 0) {velocity.modify(velocity.getPointX() - 1, velocity.getPointY());} else if (velocity.getPointX() < 0) {velocity.modify(velocity.getPointX() + 1, velocity.getPointY());}// 检查是否停止移动if (Math.abs(velocity.getPointX()) < 3 && velocity.getPointY() > 20) {isMoving = false;}}
}
3.2 篮筐类(Hoop.java)
package com.example.basketball;import ohos.agp.utils.Point;
import ohos.agp.utils.Rect;public class Hoop {private Rect rect; // 篮筐矩形区域private int netWidth; // 篮网宽度private int netHeight; // 篮网高度public Hoop(int left, int top, int right, int bottom) {this.rect = new Rect(left, top, right, bottom);this.netWidth = right - left;this.netHeight = bottom - top;}// 检查是否进球public boolean checkScore(Basketball ball) {Point ballPos = ball.getPosition();int ballRadius = ball.getRadius();// 检查球是否穿过篮筐return ballPos.getPointX() + ballRadius > rect.left &&ballPos.getPointX() - ballRadius < rect.right &&ballPos.getPointY() + ballRadius > rect.top &&ballPos.getPointY() - ballRadius < rect.bottom;}// getter方法public Rect getRect() { return rect; }public int getNetWidth() { return netWidth; }public int getNetHeight() { return netHeight; }
}
3.3 游戏视图(GameView.java)
package com.example.basketball;import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.app.Context;
import ohos.media.image.PixelMap;public class GameView extends Component implements Component.DrawTask {private Basketball basketball;private Hoop hoop;private int score = 0;private Paint scorePaint;private Paint powerPaint;private Paint linePaint;private PixelMap basketballImage;private PixelMap hoopImage;private Point startPoint = new Point(0, 0);private Point endPoint = new Point(0, 0);private boolean isDrawingLine = false;private int powerLevel = 0;public GameView(Context context) {super(context);init();}private void init() {// 初始化画笔scorePaint = new Paint();scorePaint.setColor(Color.WHITE);scorePaint.setTextSize(50);powerPaint = new Paint();powerPaint.setColor(Color.RED);powerPaint.setStyle(Paint.Style.FILL_STYLE);linePaint = new Paint();linePaint.setColor(Color.WHITE);linePaint.setStrokeWidth(3);// 初始化篮球和篮筐int screenWidth = getWidth();int screenHeight = getHeight();basketball = new Basketball(screenWidth / 4, screenHeight - 150, 50);hoop = new Hoop(screenWidth - 200, screenHeight / 4, screenWidth - 100, screenHeight / 4 + 50);// 加载图片资源basketballImage = loadPixelMap("entry/resources/base/media/basketball.png");hoopImage = loadPixelMap("entry/resources/base/media/hoop.png");addDrawTask(this);setTouchEventListener(this::onTouchEvent);}@Overridepublic void onDraw(Component component, Canvas canvas) {// 绘制背景canvas.drawColor(Color.BLUE);// 绘制篮筐Rect hoopRect = hoop.getRect();if (hoopImage != null) {canvas.drawPixelMapHolder(hoopImage.createPixelMapHolder(), hoopRect.left, hoopRect.top);} else {Paint hoopPaint = new Paint();hoopPaint.setColor(Color.ORANGE);canvas.drawRect(hoopRect, hoopPaint);}// 绘制篮球Point ballPos = basketball.getPosition();if (basketballImage != null) {canvas.drawPixelMapHolder(basketballImage.createPixelMapHolder(), ballPos.getPointX() - basketball.getRadius(), ballPos.getPointY() - basketball.getRadius());} else {Paint ballPaint = new Paint();ballPaint.setColor(Color.ORANGE);canvas.drawCircle(ballPos.getPointX(), ballPos.getPointY(), basketball.getRadius(), ballPaint);}// 绘制力量指示线if (isDrawingLine) {canvas.drawLine(startPoint.getPointX(), startPoint.getPointY(),endPoint.getPointX(), endPoint.getPointY(), linePaint);// 绘制力量条int powerWidth = 200;int powerHeight = 30;int powerX = 50;int powerY = 50;canvas.drawRect(powerX, powerY, powerX + powerWidth, powerY + powerHeight, new Paint().setColor(Color.GRAY));canvas.drawRect(powerX, powerY, powerX + powerLevel * 2, powerY + powerHeight, powerPaint);}// 绘制分数canvas.drawText("得分: " + score, 50, 100, scorePaint);}private boolean onTouchEvent(Component component, TouchEvent event) {int x = (int) event.getPointerPosition(0).getX();int y = (int) event.getPointerPosition(0).getY();switch (event.getAction()) {case TouchEvent.PRIMARY_POINT_DOWN:if (!basketball.isMoving()) {startPoint.modify(x, y);isDrawingLine = true;powerLevel = 0;}break;case TouchEvent.POINT_MOVE:if (isDrawingLine) {endPoint.modify(x, y);powerLevel = calculatePower(startPoint, endPoint);invalidate();}break;case TouchEvent.PRIMARY_POINT_UP:if (isDrawingLine && !basketball.isMoving()) {shootBall(startPoint, endPoint);isDrawingLine = false;invalidate();}break;}return true;}private int calculatePower(Point start, Point end) {// 计算两点之间的距离作为力量值double distance = Math.sqrt(Math.pow(end.getPointX() - start.getPointX(), 2) + Math.pow(end.getPointY() - start.getPointY(), 2));return Math.min((int)distance / 5, 100); // 限制最大力量为100}private void shootBall(Point start, Point end) {// 计算投篮方向和力量int powerX = (start.getPointX() - end.getPointX()) / 5;int powerY = (start.getPointY() - end.getPointY()) / 5;basketball.setVelocity(powerX, powerY);// 启动游戏循环startGameLoop();}private void startGameLoop() {// 使用定时器更新游戏状态getContext().getUITaskDispatcher().delayDispatch(() -> {basketball.update();// 检查是否进球if (hoop.checkScore(basketball)) {score++;resetBall();}// 检查球是否出界if (basketball.getPosition().getPointY() > getHeight()) {resetBall();}invalidate();if (basketball.isMoving()) {startGameLoop();}}, 16); // 约60帧/秒}private void resetBall() {basketball.setPosition(getWidth() / 4, getHeight() - 150);basketball.setVelocity(0, 0);}private PixelMap loadPixelMap(String path) {// 实现图片加载逻辑// 实际项目中应该使用ResourceManager加载资源return null;}
}
3.4 主界面(MainAbilitySlice.java)
package com.example.basketball;import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Button;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.Text;
import ohos.agp.window.dialog.ToastDialog;public class MainAbilitySlice extends AbilitySlice {private GameView gameView;@Overridepublic void onStart(Intent intent) {super.onStart(intent);DirectionalLayout layout = new DirectionalLayout(this);layout.setOrientation(DirectionalLayout.VERTICAL);// 游戏视图gameView = new GameView(this);// 按钮布局DirectionalLayout buttonLayout = new DirectionalLayout(this);buttonLayout.setOrientation(DirectionalLayout.HORIZONTAL);buttonLayout.setPadding(10, 10, 10, 10);// 重新开始按钮Button resetButton = new Button(this);resetButton.setText("重新开始");resetButton.setClickedListener(component -> gameView.resetGame());// 帮助按钮Button helpButton = new Button(this);helpButton.setText("帮助");helpButton.setClickedListener(component -> showHelp());buttonLayout.addComponent(resetButton);buttonLayout.addComponent(helpButton);layout.addComponent(gameView);layout.addComponent(buttonLayout);super.setUIContent(layout);}private void showHelp() {new ToastDialog(this).setText("游戏玩法:\n1. 按住篮球并拖动\n2. 释放手指投篮\n3. 力量越大投得越远").show();}public void resetGame() {gameView.resetGame();}
}
4. 布局文件(ability_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayoutxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:width="match_parent"ohos:height="match_parent"ohos:orientation="vertical"ohos:background_element="#3498db"><com.example.basketball.GameViewohos:id="$+id:game_view"ohos:width="match_parent"ohos:height="0vp"ohos:weight="1"/><DirectionalLayoutohos:id="$+id:button_layout"ohos:width="match_parent"ohos:height="wrap_content"ohos:orientation="horizontal"ohos:padding="10vp"ohos:margin="10vp"ohos:background_element="#2980b9"><Buttonohos:id="$+id:reset_button"ohos:width="0vp"ohos:height="50vp"ohos:weight="1"ohos:text="重新开始"ohos:text_size="16fp"ohos:margin="5vp"ohos:background_element="#2ecc71"/><Buttonohos:id="$+id:help_button"ohos:width="0vp"ohos:height="50vp"ohos:weight="1"ohos:text="帮助"ohos:text_size="16fp"ohos:margin="5vp"ohos:background_element="#e74c3c"/></DirectionalLayout>
</DirectionalLayout>
5. 游戏物理实现要点
5.1 投篮物理模拟
public void update() {if (!isMoving) return;// 更新位置position.modify(position.getPointX() + velocity.getPointX(), position.getPointY() + velocity.getPointY());// 模拟重力velocity.modify(velocity.getPointX(), velocity.getPointY() + 2);// 模拟空气阻力if (velocity.getPointX() > 0) {velocity.modify(velocity.getPointX() - 1, velocity.getPointY());} else if (velocity.getPointX() < 0) {velocity.modify(velocity.getPointX() + 1, velocity.getPointY());}// 检查是否停止移动if (Math.abs(velocity.getPointX()) < 3 && velocity.getPointY() > 20) {isMoving = false;}
}
5.2 投篮力量计算
private void shootBall(Point start, Point end) {// 计算投篮方向和力量int powerX = (start.getPointX() - end.getPointX()) / 5;int powerY = (start.getPointY() - end.getPointY()) / 5;basketball.setVelocity(powerX, powerY);// 启动游戏循环startGameLoop();
}