【pgzero】手把手教你制作游戏——Pong

前言

今天要做的游戏叫Pong,它是世界上第一款街机游戏。(关键它好做)

制作游戏的引擎有很多,比如UnityConstruct . . . . . . ...... ......而今天我们用的既不是Unity,也不是Construct,而是Python

制作游戏的模块是pgzero,它是在 Python 比较有名的库 Pygame 上又加了一层封装,对初学者制作游戏非常友好。

安装pgzero

pgzero可以进入cmd,然后用“pip install pgzero”来安装。(前提是你得先安装Python)

完成以后,就可以开始你的游戏制作之旅了 😃

角色

首先,先写好这两行代码:

import pgzrun

pgzrun.go()

运行这两行代码,你就会发现:屏幕中多了一个黑色(指背景)窗口!

这就是pgzero的神奇之处。如果有了解过pygame的朋友肯定知道:要想让游戏窗口正常的运行,必须写上很多的代码才可以,有时候还会记错一些。

言归正传,再写上这几行代码:

import pgzrun
import os

os.environ["SDL_VIDEO_CENTERED"] = '1'

WIDTH = 1000
HEIGHT = 800

pad1 = Rect((20, 20), (10, 100))

def draw():
	screen.clear()
	screen.draw.filled_rect(pad1, "white")

pgzrun.go()
  1. os.environ["SDL_VIDEO_CENTERED"] = '1' 用来设定pgzero窗口为居中。
  2. WIDTHHEIGHT用来定义窗口的长宽。
  3. pad1 = Rect((20, 20), (10, 100))用来定义一个矩形,参数一是矩形的位置,参数二是矩形的长宽。
  4. screen.draw.filled_rect(pad1, "white")用来填充矩形,第一个参数是目标矩形。第二个参数是颜色,与(255, 255, 255)等价。

如法炮制,再写(粘贴)出第二个球拍的代码:

import pgzrun
import os

os.environ["SDL_VIDEO_CENTERED"] = '1'

WIDTH = 1000
HEIGHT = 800

pad1 = Rect((20, 20), (10, 100))
pad1 = Rect((WIDTH - 40, 20), (10, 100))

def draw():
	screen.clear()
	screen.draw.filled_rect(pad1, "white")

pgzrun.go()

然后定义一个类,类名是 Ball,写上这样的代码:

class Ball:
	def __init__(self):
		self.pos = [WIDTH / 2, HEIGHT / 2]
		self.speed = [200, 200]

这段代码定义了一个球,有pos(位置)和speed(速度)两个列表。有人可能会问,speed 为什么也是列表,就不能是普通的 int 吗?而且 speed 里的元素这么大,这小球移动的速度不应该是非常快吗?这些代码都有什么用呢?

别着急,这段代码在后面有大用。

先实例化Ball类:

ball = Ball()

然后最关键的代码来了:在 screen 里面填充我们的小球。

screen.draw.filled_circle(ball.pos, 4, "white")

其中第一个参数是填充的位置,第二个参数是小球的半径,第三个参数是颜色。

这是现在的代码:

import pgzrun
import os

os.environ["SDL_VIDEO_CENTERED"] = '1'

WIDTH = 1000
HEIGHT = 800

class Ball:
	def __init__(self):
		self.pos = [WIDTH / 2, HEIGHT / 2]
		self.speed = [200, 200]
ball = Ball()

pad1 = Rect((20, 20), (10, 100))
pad2 = Rect((WIDTH - 40, 20), (10, 100))

def draw():
	screen.clear()
	screen.draw.filled_rect(pad1, "white")
	screen.draw.filled_rect(pad2, "white")
	screen.draw.filled_circle(ball.pos, 4, "white")

pgzrun.go()

截至目前为止,角色已经定义好了,接下来就是让他们动起来。

让他们动起来吧!

首先先定义一个 update 的函数,参数是 dt,像这样:

def update():
	pass    # 这是占位用的

随后写上让小球移动的代码:

for i in range(2):
		ball.pos[i] += speed[i] * dt

其中ball.pos[i] = speed[i] * dt是计算出小球在下一帧的位置,这就是为什么 speed 列表中的元素会这么大的原因,但是我这个代码不是唯一的,因为不同的电脑性能不同,帧数(fps)也不同。

你可能会说:循环里的代码到底是什么意思,实现的又是什么?

这个嘛…其实就是路程=速度×时间

运行上面的代码,我们就能得到一个会动的小球,但是多运行一会儿后你就会发现:小球离开屏幕后就不回来了。那么我们就能到update函数里面添加代码:

if ball.pos[1] >= HEIGHT or ball.pos[1] <= 0:
		ball.speed[1] *= -1

这两行代码是为了检测小球是否碰到上下边缘,如果碰到,那就让小球反弹。

接下来就是检测小球是否碰到两边,如果碰到,就直接重置小球。

在这之前,我们要先解决两个问题:

  • 小球碰到边缘后怎么重置?
  • 如何检测碰到边缘?

首先解决第一个问题:小球碰到边缘后重置。

这个问题很简单,只需修改Ball类的代码:

class Ball:
	def __init__(self):
		self.reset()
	
	def reset(self):
		self.pos = [WIDTH / 2, HEIGHT / 2]
		self.speed = [200, 200]
  • 小球碰到边缘后重置

再解决第二个问题,如何检测碰到边缘。

update函数里添加代码:

if ball.pos[0] >= WIDTH or ball.pos[0] <= 0:
		ball.reset()

这样就实现了小球碰到边缘的检测。

  • 小球碰到边缘的检测

小球的问题解决了。接下来应该看球拍了。

在Pong里,小球碰到球拍是能动的,如何让球拍动呢?可以用keyboard检测按键是否被按下。(直接用就可以了 ,是不是很人性化

首先先新建一个常量:PAD_SPEED,把它赋值为10

然后检测按键:

if keyboard.w:
		pad1.y -= PAD_SPEED
	elif keyboard.s:
		pad1.y += PAD_SPEED

	if keyboard.up:
		pad2.y -= PAD_SPEED
	elif keyboard.down:
		pad2.y += PAD_SPEED

球拍能动了,如何检测小球碰到拍子呢?这就涉及到一个函数:collidepoint

collidepoint是用来检测角色碰到坐标点的,可以这样用:

a.collidepoint(b)

根据这个函数我们就能写出如下代码:

if pad1.collidepoint(ball.pos) or pad2.collidepoint(ball.pos):
		ball.speed[0] *= -1

把以上所有的代码都按照提示写进你的代码后,你的代码应该是这样的:

import pgzrun
import os
import time

os.environ["SDL_VIDEO_CENTERED"] = '1'

WIDTH = 1000
HEIGHT = 800
PAD_SPEED = 10

pad1 = Rect((20, 20), (10, 100))
pad2 = Rect((WIDTH - 20, 20), (10, 100))

class Ball:
	def __init__(self):
		self.reset()
	
	def reset(self):
		self.pos = [WIDTH / 2, HEIGHT / 2]
		self.speed = [200, 200]


ball = Ball()


def draw():
	screen.clear()
	screen.draw.filled_rect(pad1, "white")
	screen.draw.filled_rect(pad2, "white")
	screen.draw.filled_circle(ball.pos, 4, "white")



def update(dt):

	if keyboard.w:
		pad1.y -= PAD_SPEED
	elif keyboard.s:
		pad1.y += PAD_SPEED

	if keyboard.up:
		pad2.y -= PAD_SPEED
	elif keyboard.down:
		pad2.y += PAD_SPEED

	for i in range(2):
		ball.pos[i] += ball.speed[i] * dt

	if ball.pos[1] >= HEIGHT or ball.pos[1] <= 0:
		ball.speed[1] *= -1

	if ball.pos[0] >= WIDTH or ball.pos[0] <= 0:
		ball.reset()

	if pad1.collidepoint(ball.pos) or pad2.collidepoint(ball.pos):
		ball.speed[0] *= -1


pgzrun.go()

写到这,游戏的基本功能就写完了,接下来就是给他添加分数列表,小球在哪重置,就给对面的玩家加分,修改后代码是这样的:

import pgzrun
import os
import time

os.environ["SDL_VIDEO_CENTERED"] = '1'

WIDTH = 1000
HEIGHT = 800
PAD_SPEED = 10
scores = [0, 0]

pad1 = Rect((20, 20), (10, 100))
pad2 = Rect((WIDTH - 20, 20), (10, 100))

class Ball:
	def __init__(self):
		self.reset()
	
	def reset(self):
		self.pos = [WIDTH / 2, HEIGHT / 2]
		self.speed = [200, 200]


ball = Ball()


def draw():
	global scores
	screen.clear()
	screen.draw.filled_rect(pad1, "white")
	screen.draw.filled_rect(pad2, "white")
	screen.draw.filled_circle(ball.pos, 4, "white")
	screen.draw.text(":", midtop=(WIDTH // 2, 20), fontname=None, fontsize=64)
	screen.draw.text(f"{scores[0]}", topright=(WIDTH // 2 - 40, 20), fontname=None, fontsize=64)
	screen.draw.text(f"{scores[1]}", topright=(WIDTH // 2 + 60, 20), fontname=None, fontsize=64)



def update(dt):
	global scores
	if keyboard.w:
		pad1.y -= PAD_SPEED
	elif keyboard.s:
		pad1.y += PAD_SPEED

	if keyboard.up:
		pad2.y -= PAD_SPEED
	elif keyboard.down:
		pad2.y += PAD_SPEED

	for i in range(2):
		ball.pos[i] += ball.speed[i] * dt

	if ball.pos[1] >= HEIGHT or ball.pos[1] <= 0:
		ball.speed[1] *= -1

	if ball.pos[0] <= 0:
		scores[1] += 1
		ball.reset()
	if ball.pos[0] >= WIDTH:
		scores[0] += 1
		ball.reset()

	if pad1.collidepoint(ball.pos) or pad2.collidepoint(ball.pos):
		ball.speed[0] *= -1


pgzrun.go()

其他优化

如果还嫌做的不够好,还可以用random模块实现方向随机和位置随机(作者这里做的不太好)

import pgzrun
import os
import random

os.environ["SDL_VIDEO_CENTERED"] = '1'

WIDTH = 1000
HEIGHT = 800
PAD_SPEED = 10
scores = [0, 0]

pad1 = Rect((20, 20), (10, 100))
pad2 = Rect((WIDTH - 20, 20), (10, 100))

class Ball:
	def __init__(self):
		self.reset()
	
	def reset(self):
		self.pos = [WIDTH / 2, random.randint(1, HEIGHT - 1)]
		randomx = random.randint(200, 400)
		randomy = random.randint(200, 400)
		sx = random.choice([randomx, randomx * -1])
		sy = random.choice([randomy, randomy * -1])
		self.speed = [sx, sy]


ball = Ball()


def draw():
	global scores
	screen.clear()
	screen.draw.filled_rect(pad1, "white")
	screen.draw.filled_rect(pad2, "white")
	screen.draw.filled_circle(ball.pos, 4, "white")
	screen.draw.text(":", midtop=(WIDTH // 2, 20), fontname=None, fontsize=64)
	screen.draw.text(f"{scores[0]}", topright=(WIDTH // 2 - 40, 20), fontname=None, fontsize=64)
	screen.draw.text(f"{scores[1]}", topright=(WIDTH // 2 + 60, 20), fontname=None, fontsize=64)



def update(dt):
	global scores
	if keyboard.w:
		pad1.y -= PAD_SPEED
	elif keyboard.s:
		pad1.y += PAD_SPEED

	if keyboard.up:
		pad2.y -= PAD_SPEED
	elif keyboard.down:
		pad2.y += PAD_SPEED

	for i in range(2):
		ball.pos[i] += ball.speed[i] * dt

	if ball.pos[1] >= HEIGHT or ball.pos[1] <= 0:
		ball.speed[1] *= -1

	if ball.pos[0] <= 0:
		scores[1] += 1
		ball.reset()
	if ball.pos[0] >= WIDTH:
		scores[0] += 1
		ball.reset()

	if pad1.collidepoint(ball.pos) or pad2.collidepoint(ball.pos):
		ball.speed[0] *= -1


pgzrun.go()

以上就是关于pgzero制作游戏的教程了,希望你们喜欢。

球关注
  • 38
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我可以为您提供一个基本的HTML和JavaScript代码,用于还原Pong游戏。以下是代码: HTML代码: ``` <!DOCTYPE html> <html> <head> <title>Pong Game</title> <style type="text/css"> canvas { border: 1px solid black; } </style> </head> <body> <canvas id="canvas" width="500" height="300"></canvas> <script src="pong.js"></script> </body> </html> ``` JavaScript代码(保存为“pong.js”): ``` var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); var ballRadius = 10; var x = canvas.width/2; var y = canvas.height-30; var dx = 2; var dy = -2; var paddleHeight = 10; var paddleWidth = 75; var paddleX = (canvas.width-paddleWidth)/2; var rightPressed = false; var leftPressed = false; document.addEventListener("keydown", keyDownHandler, false); document.addEventListener("keyup", keyUpHandler, false); function keyDownHandler(e) { if(e.keyCode == 39) { rightPressed = true; } else if(e.keyCode == 37) { leftPressed = true; } } function keyUpHandler(e) { if(e.keyCode == 39) { rightPressed = false; } else if(e.keyCode == 37) { leftPressed = false; } } function drawBall() { ctx.beginPath(); ctx.arc(x, y, ballRadius, 0, Math.PI*2); ctx.fillStyle = "#0095DD"; ctx.fill(); ctx.closePath(); } function drawPaddle() { ctx.beginPath(); ctx.rect(paddleX, canvas.height-paddleHeight, paddleWidth, paddleHeight); ctx.fillStyle = "#0095DD"; ctx.fill(); ctx.closePath(); } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); drawBall(); drawPaddle(); if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) { dx = -dx; } if(y + dy < ballRadius) { dy = -dy; } else if(y + dy > canvas.height-ballRadius) { if(x > paddleX && x < paddleX + paddleWidth) { dy = -dy; } else { alert("Game Over"); document.location.reload(); } } if(rightPressed && paddleX < canvas.width-paddleWidth) { paddleX += 7; } else if(leftPressed && paddleX > 0) { paddleX -= 7; } x += dx; y += dy; } setInterval(draw, 10); ``` 这个代码会在网页上显示一个带有边框的画布,然后在画布中创建一个小球和一个移动的挡板。您可以通过按下箭头键来移动挡板,目标是让小球反弹,不要让它触底。如果小球触底,游戏结束并重新加载页面。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值