目录
0、项目介绍
这次是一个有意思的计算机视觉游戏——贪吃蛇,我们以食指为蛇头,不断的移动我们的手指,当吃到甜甜圈的时候,蛇身增长,当食指停止或触碰蛇身时,游戏结束。点击'r'将重新开始游戏。
1、效果展示
制作成GIF图片的效果有点卡,实际的效果还是很好的。
2、项目搭建
————17 Gluttonous snake
——Donut.png
——snake.py
3、项目代码展示与讲解
#snake.py
import math
import random
import cvzone
import cv2
import numpy as np
from cvzone.HandTrackingModule import HandDetector
cap = cv2.VideoCapture(0)
cap.set(3, 1280)
cap.set(4, 720)
detector = HandDetector(detectionCon=0.8, maxHands=1)
class SnakeGameClass:
def __init__(self, pathFood):
self.points = [] # 蛇的所有点
self.lengths = [] # 每个点之间的距离
self.currentLength = 0 # 蛇的总长度
self.allowedLength = 150 # 总共允许长度
self.previousHead = 0, 0 # 起点
self.imgFood = cv2.imread(pathFood, cv2.IMREAD_UNCHANGED) # 读取甜甜圈的位置路径
self.hFood, self.wFood, _ = self.imgFood.shape # 得到甜甜圈图像的长、宽
self.foodPoint = 0, 0 # 初始化甜甜圈的位置
self.randomFoodLocation() # 随机分布甜甜圈的位置
self.score = 0 # 吃得甜甜圈的分数
self.gameOver = False # gameover
def randomFoodLocation(self):
self.foodPoint = random.randint(100, 1000), random.randint(100, 600)
def update(self, imgMain, currentHead):
if self.gameOver:
cvzone.putTextRect(imgMain, "Game Over", [300, 400],
scale=7, thickness=5, offset=20)
cvzone.putTextRect(imgMain, f'Your Score: {self.score}', [300, 550],
scale=7, thickness=5, offset=20)
else:
px, py = self.previousHead
cx, cy = currentHead
self.points.append([cx, cy])
distance = math.hypot(cx - px, cy - py)
self.lengths.append(distance)
self.currentLength += distance
self.previousHead = cx, cy
# Length Reduction
if self.currentLength > self.allowedLength:
for i, length in enumerate(self.lengths):
self.currentLength -= length
self.lengths.pop(i)
self.points.pop(i)
if self.currentLength < self.allowedLength:
break
# Check if snake ate the Food
rx, ry = self.foodPoint
if rx - self.wFood // 2 < cx < rx + self.wFood // 2 and \
ry - self.hFood // 2 < cy < ry + self.hFood // 2:
self.randomFoodLocation()
self.allowedLength += 50
self.score += 1
print(self.score)
# Draw Snake
if self.points:
for i, point in enumerate(self.points):
if i != 0:
cv2.line(imgMain, self.points[i - 1], self.points[i], (0, 0, 255), 20)
cv2.circle(imgMain, self.points[-1], 20, (0, 255, 0), cv2.FILLED)
# Add Food
imgMain = cvzone.overlayPNG(imgMain, self.imgFood,
(rx - self.wFood // 2, ry - self.hFood // 2))
cvzone.putTextRect(imgMain, f'Score: {self.score}', [50, 80],
scale=3, thickness=3, offset=10)
# Check for Collision
pts = np.array(self.points[:-2], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.polylines(imgMain, [pts], False, (0, 255, 0), 3)
minDist = cv2.pointPolygonTest(pts, (cx, cy), True)
if -1 <= minDist <= 1:
print("Hit")
self.gameOver = True
self.points = [] # all points of the snake
self.lengths = [] # distance between each point
self.currentLength = 0 # total length of the snake
self.allowedLength = 150 # total allowed Length
self.previousHead = 0, 0 # previous head point
self.randomFoodLocation()
return imgMain
game = SnakeGameClass("Donut.png")
while True:
success, img = cap.read()
img = cv2.flip(img, 1)
hands, img = detector.findHands(img, flipType=False)
if hands:
lmList = hands[0]['lmList']
pointIndex = lmList[8][0:2]
img = game.update(img, pointIndex)
cv2.imshow("Image", img)
key = cv2.waitKey(1)
if key == ord('r'):
game.gameOver = False
elif key==27:break
首先,打开摄像头的窗口得比较的大,不仅为了方便测试,而且窗口小了也会影响我们的手势识别,然后建立SnakeGameClass类,其下的变量名用途请查看注释。
其中,先看while True里面,cv2.flip函数是用窗口画面翻转的,输入一个正值,便会绕y轴翻转,这样能够保证窗口画面中的动作与我们的想法相同,接下来获得我们的手指landmark,即是获得目前蛇的头部landmark,之后完成我们的游戏更新部分。
其次,来看randomFoodLocation()函数,可以用于随机生成甜甜圈所在位置,我们为边缘留了些位置,避免手势识别失误和容易出界的问题。此时它并不具备更新位置的能力(只是生成了一个随机数位置),我们用update函数来完成主要的功能。我们想要实现的一个效果是游戏结束时,我需要得到一个提示,你不能再继续下去了,就如上面的GIF图片中,当我手势被识别时,会有一个提示(用putTextRcet完成),否则就会进行正常的游戏操作。
正常的游戏操作,我希望有这几个方面,甜甜圈覆盖窗口画面实现更新、蛇头与蛇身相撞就会gameover、手势一旦识别必须不停移动、吃掉一个甜甜圈增长一节蛇身等。
- Length Reduction:
一旦蛇的总长度超过了我们允许的长度,我们就需要对其进行缩减,这里使用了enumerate函数对列表进行了迭代,返回了一个索引和值,方便减去对应的距离。
- Check if snake ate the Food:
只要蛇头进入了甜甜圈的范围内,即可认为是吃到了甜甜圈,接下来就可以完成食物位置的更新,蛇身的加长,score得分。
- Draw Snake:
画蛇的初始态。
- Add Food:
在窗口画面中覆盖上甜甜圈,可以使用cvzone.overlayPNG函数。
- Check for Collision:
[pts]为多边形曲线阵列,在这里我们将会绘制一个多边形,并用cv2.pointPolygonTest来返回除最后两个点外与蛇头最近的距离,因为有tickness的原因,所以我们无需纠结于minDist一定小于等于0。
4、项目资源
GitHub:17 Gluttonous snake
5、项目总结
最近本来在准备期末考试的,所以很久没写博客了,但由于疫情原因提前返乡回家了,这学期的考试要得等到明年啦。想要添加按钮、声音、毒药这些的,没有精力搞了,有兴趣的大家可以试试。