TS实现贪吃蛇项目(附源码)

前言

ts是一门面向对象的语言,在学习完后,可以拿贪吃蛇小游戏来当作一个练手项目。

项目效果图

snake

一、项目详细介绍

1、主面板

<!--创建游戏的主容器-->
<div id="main">
    <!--    设置游戏的舞台-->
    <div id="stage">
        <!--设置蛇-->
        <div id="snake">
            <!--snake内部的div 表示蛇的各部分-->
            <div></div>
        </div>
        <!--设置食物-->
        <div id="food">
            <!--添加四个小div来设置食物的样式-->
            <div></div>
            <div></div>
            <div></div>
            <div></div>
        </div>
    </div>
    <!--    设置游戏的积分牌-->
    <div id="score-panel">
        <div>
            SCORE: <span id="score">0</span>
        </div>
        <div>
            level: <span id="level">1</span>
        </div>
    </div>
</div>

2、食物Food类

一个对象所包含的主要是属性和方法两点。
食物所具有的属性是位置,而方法则是改变坐标位置。

// 定义食物类Food
class Food {
    // 定义一个属性表示食物所对应的元素
    element: HTMLElement;

    constructor() {
        // 获取页面中的food元素并将其赋值给element
        this.element = document.getElementById("food") as HTMLElement;
    }

    // 定义一个获取食物X轴坐标的方法
    get X() {
        return this.element.offsetLeft;
    }

    // 定义一个获取食物Y轴坐标的方法
    get Y() {
        return this.element.offsetTop;
    }

    // 修改食物位置的方法
    change() {
        // 生成一个随机的位置
        // 食物的位置最小是0,最大是290(减去自身)
        // 蛇移动一次就是一格,一格的大小就是10,所以就要求食物的坐标必须是10的倍数

        let left = Math.round(Math.random() * 29) * 10;
        let top = Math.round(Math.random() * 29) * 10;

        this.element.style.left = left + 'px';
        this.element.style.top = top + 'px';
    }
}

export default Food;

3、计分盘ScorePanel类

计分盘应包含分数和等级2个属性。
实现此类的思路是:蛇每吃到一个食物就加一分,当加到多少分(该分数自己设定的)时就等级+1。

// 定义表示记分牌的类
class ScorePanel {
    // score和level用来记录分数和等级
    score = 0;
    level = 1;

    // 分数和等级所在的元素,在构造函数中进行初始化
    scoreEle: HTMLElement;
    levelEle: HTMLElement;

    // 设置一个变量限制等级
    maxLevel: number;
    // 设置一个变量表示多少分时升级
    upScore: number;

    constructor(maxLevel: number = 10,upScore: number = 10) {
        this.scoreEle = document.getElementById("score") as HTMLElement;
        this.levelEle = document.getElementById("level") as HTMLElement;
        this.maxLevel = maxLevel;
        this.upScore = upScore;
    }

    // 设置一个加分的方法
    addScore() {
        // 使分数自增
        this.score++;
        this.scoreEle.innerHTML = this.score + '';
        // 判断分数是多少
        if(this.score % this.upScore === 0) {
            this.levelUp();
        }
    }

    // 提升等级的方法
    levelUp() {
        if(this.level < this.maxLevel) {
            this.levelEle.innerHTML = ++this.level + '';
        }
    }
}

export default ScorePanel;

4、蛇Snake类

蛇的类是最复杂的。
蛇需要拆分为头和身体两部分。当吃到一个食物后,蛇就会增加身体。
蛇在运行过程中不能掉头,也不能穿过自己的身体。

/*
蛇在运行过程中不能掉头,也不能穿过自己的身体
 */

// 蛇的类
class Snake {
    // 表示蛇头的元素
    head: HTMLElement;
    // 蛇的身体(包括蛇头)
    bodies: HTMLCollection;
    // 获取蛇的容器
    element: HTMLElement;

    constructor() {
        this.element = document.getElementById('snake')!;
        this.head = document.querySelector('#snake > div') as HTMLElement;
        this.bodies = this.element.getElementsByTagName("div");
    }

    // 获取蛇头的坐标
    get X() {
        return this.head.offsetLeft;
    }
    get Y() {
        return this.head.offsetTop;
    }

    // 设置蛇头的坐标
    set X(value) {
        // 如果新值和旧值相同,则直接返回不再修改
        if(this.X === value) {
            return;
        }

        // X的值的合法范围0-290之间
        if(value < 0 || value > 290) {
            // 进入判断说明蛇撞墙了,抛出一个异常
            throw new Error('蛇撞墙了');
        }

        // 修改x时,是在修改水平坐标,蛇在左右移动。蛇在向左移动时,不能向右掉头,反之亦然。
        // 蛇头的位置等于第二个身体的位置时,说明掉头了
        if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
            // console.log('水平方向发生了掉头!');

            // 如果发生了掉头,让蛇向反方向继续移动
            if(value > this.X) {
                // 如果新增value大于旧值X,则说明蛇在向右走,此时是掉头的状态,应该使蛇继续向左走。
                value = this.X - 10;
            } else {
                value = this.X + 10;
            }
        }

        // 移动身体
        this.moveBody();

        this.head.style.left = value + 'px';

        // 检查有没有撞到自己
        this.checkHeadBody();
    }
    set Y(value) {
        // 如果新值和旧值相同,则直接返回不再修改
        if(this.Y === value) {
            return;
        }

        // Y的值的合法范围0-290之间
        if(value < 0 || value > 290) {
            // 进入判断说明蛇撞墙了,抛出一个异常
            throw new Error('蛇撞墙了');
        }

        // 修改y时,是在修改垂直坐标,蛇在上下移动。蛇在向上移动时,不能向下掉头,反之亦然。
        // 蛇头的位置等于第二个身体的位置时,说明掉头了
        if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
            // console.log('垂直方向发生了掉头!');

            if(value > this.Y) {
                value = this.Y - 10;
            } else {
                value = this.Y + 10;
            }
        }

        // 移动身体
        this.moveBody();

        this.head.style.top = value + 'px';

        // 检查有没有撞到自己
        this.checkHeadBody();
    }

    // 蛇增加身体的方法
    addBody() {
        // 向element中添加一个div(方法一)
        this.element.insertAdjacentHTML('beforeend',"<div></div>");

        // 向element中添加一个div(方法二)
        // let newBody = document.createElement('div');
        // this.element.appendChild(newBody);
    }

    // 添加一个蛇身体移动的方法
    moveBody() {
        /*
        将后边的身体设置为前边的身体的位置
            举例子:
                第4节 = 第3节的位置
                第3节 = 第2节的位置
                第2节 = 蛇头的位置
         */

        // 遍历获取所有的身体
        for(let i=this.bodies.length-1; i>0; i--){
            // 获取前边身体的位置
            let X = (this.bodies[i-1] as HTMLElement).offsetLeft;
            let Y = (this.bodies[i-1] as HTMLElement).offsetTop;

            // 将值设置到当前身体上
            (this.bodies[i] as HTMLElement).style.left = X + 'px';
            (this.bodies[i] as HTMLElement).style.top = Y + 'px';
        }
    }

    checkHeadBody(){
        // 获取所有的身体,检查其是否和蛇头的坐标发生重叠
        for(let i = 1; i < this.bodies.length; i++) {
            let bd = this.bodies[i] as HTMLElement;
            if(this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
                // 进入判断说明蛇头撞到了自己,游戏结束
                throw new Error('撞到自己了!')
            }
        }
    }
}

export default Snake;

5、游戏控制GameControl类

控制上述的三个类,实现游戏的运行。
定义一个可以通过使用键盘方向键来控制蛇移动方向的方法。
以及判断蛇是否吃到食物的方法。

// 引入其他的类
import Snake from './Snake';
import Food from './Food';
import ScorePanel from "./ScorePanel";

// 游戏控制器,控器其他的所有类
class GameControl {
    // 定义三个属性
    // 蛇
    snake: Snake;
    // 食物
    food: Food;
    // 记分牌
    scorePanel: ScorePanel;

    // 创建一个属性来存储蛇的移动方向(也就是按键的方向)
    direction: string = 'Right';
    // 创建一个属性用来记录游戏是否结束
    isLive = true;

    constructor() {
        this.snake = new Snake();
        this.food = new Food();
        // this.scorePanel = new ScorePanel(10,2);
        this.scorePanel = new ScorePanel();

        this.init();
    }

    // 游戏的初始化方法,调用后游戏即开始
    init() {
        // 绑定键盘按键按下的事件
        document.addEventListener('keydown',this.keydownHandler.bind(this));
        this.run();
    }

    /*
    chrome:
        ArrowUp
        ArrowDown
        ArrowLeft
        ArrowRight

    ie:
        Up
        Down
        Left
        Right
     */

    // 创建一个键盘按下的响应函数
    keydownHandler(event: KeyboardEvent) {
        // console.log(event.key);

        // 需要检查event.key的值是否合法(用户是否按了正确的按键)
        // 修改direction属性
        this.direction = event.key;
    }

    // 创建一个控制蛇移动的方法
    run() {
        /*
        向上  Y减少
        向下  Y增加
        向左  X减少
        向右  X增加
         */

        // 获取蛇现在的坐标
        let X = this.snake.X;
        let Y = this.snake.Y;

        // 根据按键方向来修改X值和Y值
        switch (this.direction) {
            case "ArrowUp":
            case "Up":
                Y -= 10;
                break;
            case "ArrowDown":
            case "Down":
                Y += 10;
                break;
            case "ArrowLeft":
            case "Left":
                X -= 10;
                break;
            case "ArrowRight":
            case "Right":
                X += 10;
                break;
        }

        // 检查蛇是否吃到了食物
        this.checkEat(X,Y);

        // 修改蛇的X和Y值
        try {
            this.snake.X = X;
            this.snake.Y = Y;
        } catch (e) {
            // 进入到catch,说明出现了异常,游戏结束,弹出一个提示信息
            alert((e as Error).message + 'GAME OVER!');
            // 将islive设置为false
            this.isLive = false;
        }

        // 开启一个定时调用
        this.isLive && setTimeout(this.run.bind(this),300 - (this.scorePanel.level - 1)*30);
    }

    // 定义一个方法,用来检查蛇是否吃到食物
    checkEat(X: number,Y: number) {
        if(X === this.food.X && Y === this.food.Y) {
            // 食物的位置要进行重置
            this.food.change();
            // 分数增加
            this.scorePanel.addScore();
            // 蛇要增加一节
            this.snake.addBody();
        }
    }
}

export default GameControl;

二、项目源码

本人已将项目传至本人的github上了,有需要的小伙伴可以下载。
github项目开源地址:https://github.com/syj-github-hub/snake_project

项目打包命令:npm run build
项目运行命令:npm start

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
贪吃蛇项目是一个经典的游戏,适合初学者来学习Android开发。下面是一个简单的贪吃蛇项目源码: ``` MainActivity.java: import android.app.Activity; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 设置全屏 requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // 设置游戏画面 setContentView(new GameView(this)); } } GameView.java: import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import java.util.ArrayList; import java.util.List; public class GameView extends SurfaceView implements SurfaceHolder.Callback { private MainThread thread; private Snake snake; private Point food; private int score; public GameView(Context context) { super(context); getHolder().addCallback(this); thread = new MainThread(getHolder(), this); setFocusable(true); } @Override public void surfaceCreated(SurfaceHolder holder) { snake = new Snake(); food = new Point(); score = 0; thread.setRunning(true); thread.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { boolean retry = true; while (retry) { try { thread.setRunning(false); thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } retry = false; } } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { snake.updateDirection(event.getX(), event.getY()); } return true; } public void update() { snake.move(); checkCollision(); } private void checkCollision() { if (snake.checkSelfCollision() || snake.checkWallCollision(getWidth(), getHeight())) { thread.setRunning(false); } if (snake.getHead().equals(food)) { food = createFood(); snake.grow(); score += 10; } } private Point createFood() { List<Point> snakePositions = snake.getPositions(); Point newFood = new Point(); while (true) { newFood.x = (int) Math.floor(Math.random() * (getWidth() - Snake.BLOCK_SIZE)); newFood.y = (int) Math.floor(Math.random() * (getHeight() - Snake.BLOCK_SIZE)); for (Point snakePosition : snakePositions) { if (!newFood.equals(snakePosition)) { return newFood; } } } } @Override public void draw(Canvas canvas) { super.draw(canvas); if (canvas != null) { // 清空画面 canvas.drawColor(Color.BLACK); // 绘制蛇 snake.draw(canvas); // 绘制食物 Paint paint = new Paint(); paint.setColor(Color.RED); canvas.drawRect(food.x, food.y, food.x + Snake.BLOCK_SIZE, food.y + Snake.BLOCK_SIZE, paint); // 绘制分数 Paint scorePaint = new Paint(); scorePaint.setColor(Color.WHITE); scorePaint.setTextSize(40); canvas.drawText("Score: " + score, 10, 50, scorePaint); } } } Snake.java: import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import java.util.ArrayList; import java.util.List; public class Snake { public static final int BLOCK_SIZE = 40; private static final int SPEED = 10; private List<Point> positions; private int direction; public Snake() { direction = 1; positions = new ArrayList<>(); positions.add(new Point(0, 0)); positions.add(new Point(BLOCK_SIZE, 0)); positions.add(new Point(BLOCK_SIZE * 2, 0)); positions.add(new Point(BLOCK_SIZE * 3, 0)); positions.add(new Point(BLOCK_SIZE * 4, 0)); } public void updateDirection(float x, float y) { if (Math.abs(x - getHead().x) > Math.abs(y - getHead().y)) { if (x < getHead().x) { direction = 0; } else { direction = 2; } } else { if (y < getHead().y) { direction = 1; } else { direction = 3; } } } public void move() { Point newHead = new Point(getHead().x, getHead().y); switch (direction) { case 0: newHead.x -= BLOCK_SIZE; break; case 1: newHead.y -= BLOCK_SIZE; break; case 2: newHead.x += BLOCK_SIZE; break; case 3: newHead.y += BLOCK_SIZE; break; } positions.add(0, newHead); positions.remove(positions.size() - 1); } public void grow() { Point newTail = new Point(getTail().x, getTail().y); positions.add(newTail); } public boolean checkSelfCollision() { for (int i = 1; i < positions.size(); i++) { if (getHead().equals(positions.get(i))) { return true; } } return false; } public boolean checkWallCollision(int screenWidth, int screenHeight) { if (getHead().x < 0 || getHead().y < 0 || getHead().x >= screenWidth || getHead().y >= screenHeight) { return true; } return false; } public Point getHead() { return positions.get(0); } public Point getTail() { return positions.get(positions.size() - 1); } public List<Point> getPositions() { return positions; } public void draw(Canvas canvas) { Paint paint = new Paint(); paint.setColor(Color.WHITE); for (Point position : positions) { canvas.drawRect(position.x, position.y, position.x + BLOCK_SIZE, position.y + BLOCK_SIZE, paint); } } } MainThread.java: import android.graphics.Canvas; import android.view.SurfaceHolder; public class MainThread extends Thread { private final SurfaceHolder surfaceHolder; private GameView gameView; private boolean running; private Canvas canvas; public MainThread(SurfaceHolder surfaceHolder, GameView gameView) { this.surfaceHolder = surfaceHolder; this.gameView = gameView; this.running = false; } public void setRunning(boolean running) { this.running = running; } @Override public void run() { while (running) { canvas = null; try { // 锁定画布并更新游戏状态 canvas = this.surfaceHolder.lockCanvas(); synchronized (surfaceHolder) { this.gameView.update(); this.gameView.draw(canvas); } } finally { if (canvas != null) { // 解锁画布并提交更改 surfaceHolder.unlockCanvasAndPost(canvas); } } } } } ``` 这个源码中,主要的类是`MainActivity`、`GameView`、`Snake`和`MainThread`,其中`MainActivity`是整个游戏的入口类,`GameView`是游戏的主要视图类,`Snake`是贪吃蛇的逻辑实现类,`MainThread`是游戏的主线程类。 在`MainActivity`中,我们设置全屏并将游戏视图设置为`GameView`。`GameView`中,我们维护了贪吃蛇的状态和画面,并监听用户的触摸事件,更新游戏状态并绘制画面。`Snake`中,我们实现贪吃蛇的逻辑,包括移动、吃食物和检测碰撞等操作。`MainThread`中,我们使用主线程循环更新游戏状态和绘制画面。 这个贪吃蛇项目源码是一个简单的示例,供初学者参考学习。你可以根据自己的需求进行拓展和修改。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值